灵符lingfu
L

lingcoder

↓ 0 downloads

LingCoder: a Geany / VS Code-style IDE for Ling, written in Ling. Explorer, tabs, syntax-coloured editor with lint, find in files, command palette, run/build panel, and hot-reloaded extensions - including a Claude assistant.

Keywords ide, editor, ling, claude, extensions
lingfu install lingcoder
LicenseApache-2.0 OR MIT
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (34)

ling/runner.ling
# runner.ling - Run File / Run Project / Build through the platform job system; the log
# streams into the Output panel (dock.ling reads st "run_tail").

fn cache_dir() {
    bind d = p_join(app_root(), "cache")
    plat_mkdir(d)
    return d
}
fn run_log_path() { return p_join(cache_dir(), "run.log") }

# the ling executable: the setting, a build next door, or `ling` on PATH
fn ling_exe_path() {
    bind s = str_trim(st_get("ling_exe", ""))
    if s != "" { return p_norm(s) }
    bind near = p_join(p_dir(app_root()), "ling/target-coder/release/ling.exe")
    if file_exists(near) { return near }
    bind near = p_join(p_dir(app_root()), "ling/target/release/ling.exe")
    if file_exists(near) { return near }
    bind near = p_join(p_dir(app_root()), "ling/target/release/ling")
    if file_exists(near) { return near }
    return "ling"
}

fn run_busy() { return st_get("run_job", 0.0 - 1.0) >= 0.0 }

fn run_start(argv, dir, label) {
    if run_busy() { status("A job is already running - Stop it first (Shift+F5)")  return 0.0 }
    bind exe = ling_exe_path()
    bind h = plat_job_start(exe, argv, dir, run_log_path())
    if h < 0.0 { status("Could not start " + exe)  return 0.0 }
    st_set("run_job", h)
    st_set("run_label", label)
    st_set("run_t0", st_get("now", 0.0))
    st_set("run_cmdline", "$ " + p_base(exe) + " " + run_argv_text(argv))
    st_set("run_tail", st_get("run_cmdline", "") + "\n")
    st_set("show_dock", 1.0)
    st_set("dock_tab", 1.0)
    st_set("out_off", 0.0 - 1.0)
    status(label + " started")
    return 0.0
}
fn run_argv_text(argv) {
    bind s = ""
    bind i = 0.0
    while i < len(argv) {
        bind s = s + sel(i > 0.0, " ", "") + list_get(argv, i)
        bind i = i + 1.0
    }
    return s
}

fn run_file() {
    bind bi = buf_cur()
    if bi < 0.0 { status("Open a file to run")  return 0.0 }
    buf_save_all()
    bind p = buf_path(bi)
    bind e = p_ext(p)
    if syn_lang(p) == "ling" {
        run_start(["run", "--interp", p], p_dir(p), "Run " + p_base(p))
    } else if e == "bat" || e == "cmd" || e == "sh" {
        plat_launch(p)
        status("Started " + p_base(p))
    } else {
        status("Run works on .ling files (this is ." + e + ")")
    }
    return 0.0
}

# the project entry: Ling.toml's `entry = "..."`, else ling/main.ling, else main.ling
fn proj_entry() {
    bind root = proj_root()
    if root == "" { return "" }
    bind mf = p_join(root, "Ling.toml")
    if file_exists(mf) {
        bind e = unquote(str_trim(cfg_get(str_replace(plat_read(mf), " = ", "="), "entry", "")))
        if e != "" && file_exists(p_join(root, e)) { return p_join(root, e) }
    }
    if file_exists(p_join(root, "ling/main.ling")) { return p_join(root, "ling/main.ling") }
    if file_exists(p_join(root, "main.ling")) { return p_join(root, "main.ling") }
    return ""
}
fn run_project() {
    bind e = proj_entry()
    if e == "" { status("No project entry found (Ling.toml entry=, ling/main.ling or main.ling)")  return 0.0 }
    buf_save_all()
    run_start(["run", "--interp", e], proj_root(), "Run " + proj_name())
    return 0.0
}
fn run_build() {
    bind root = proj_root()
    if root == "" { status("Open a folder to build (Ctrl+Shift+O)")  return 0.0 }
    buf_save_all()
    run_start(["build", ".", "--platform", "win"], root, "Build " + proj_name())
    return 0.0
}
fn run_stop() {
    if !run_busy() { status("Nothing is running")  return 0.0 }
    plat_job_kill(st_get("run_job", 0.0 - 1.0))
    st_set("run_job", 0.0 - 1.0)
    st_set("run_tail", st_get("run_tail", "") + "\n[stopped]\n")
    status("Stopped")
    return 0.0
}
fn run_clear() {
    st_set("run_tail", "")
    plat_write(run_log_path(), "")
    return 0.0
}

# every frame: pull the log tail, notice the end
fn run_tick() {
    if !run_busy() { return 0.0 }
    # read the log twice a second (it can be large)
    if st_get("now", 0.0) - st_get("run_poll_t", 0.0) < 0.4 { return 0.0 }
    st_set("run_poll_t", st_get("now", 0.0))
    bind t = plat_job_log(run_log_path())
    bind full = st_get("run_cmdline", "") + "\n" + t
    # keep the last ~400 lines
    bind lines = str_split(full, "\n")
    bind n = len(lines)
    if n > 400.0 {
        bind keep = ""
        bind i = n - 400.0
        while i < n {
            bind keep = keep + list_get(lines, i) + sel(i < n - 1.0, "\n", "")
            bind i = i + 1.0
        }
        bind full = "[... earlier output trimmed ...]\n" + keep
    }
    st_set("run_tail", full)
    bind code = plat_job_poll(st_get("run_job", 0.0 - 1.0), run_log_path())
    if code > 0.0 - 1.5 {
        st_set("run_job", 0.0 - 1.0)
        bind secs = fmt1(st_get("now", 0.0) - st_get("run_t0", 0.0))
        st_set("run_tail", st_get("run_tail", "") + "\n[" + st_get("run_label", "") + " finished: exit " + num_str(code) + " in " + secs + " s]\n")
        status(st_get("run_label", "") + sel(code == 0.0, " finished", " FAILED (exit " + num_str(code) + ")") + " in " + secs + " s")
    }
    return 0.0
}

← Back to index