灵符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/plat_desktop.ling
# plat_desktop.ling - what the desktop runtime (ling run / ling build win|lin|mac|pios) can do.
# Every plat_* function has a twin in plat_os.ling with the same name and arguments, so the IDE
# calls these and never a native builtin that one runtime lacks.

fn plat_name() { return "desktop" }
fn plat_can_write() { return true }
fn plat_has_dialogs() { return true }

# ── files ────────────────────────────────────────────────────────────────────
fn plat_reveal(path) { fs_reveal(path) }
fn plat_dlg_open(title, exts, start) { return dlg_open(title, exts, start) }
fn plat_dlg_save(title, exts, name, start) { return dlg_save(title, exts, name, start) }
fn plat_dlg_folder(title, start) { return dlg_folder(title, start) }
fn plat_os_name() { return tm_sys_os() }
fn plat_arch() { return tm_sys_arch() }
fn plat_args() { return get_args() }
fn plat_cwd() { return fs_cwd() }
fn plat_home() { return fs_home() }
fn plat_mkdir(p) { return fs_mkdir(p) }
fn plat_copy(a, b) { return fs_copy(a, b) }
fn plat_delete(p) { return fs_delete(p) }
fn plat_rename(a, b) { return rename_file(a, b) }
fn plat_list(p) { return fs_list(p) }
fn plat_is_dir(p) { return fs_is_dir(p) }
fn plat_write(path, text) { write_file(path, text)  return true }
fn plat_read(path) {
    if !file_exists(path) { return "" }
    return read_file(path)
}
fn plat_file_size(p) { return file_size(p) }
fn plat_file_modified(p) { return file_modified(p) }
fn plat_screenshot(name) { screenshot(name) }
fn plat_close() { close_window() }
fn plat_clip_get() { return clipboard_get() }
fn plat_clip_set(s) { return clipboard_set(s) }
fn plat_env(name, d) { return env_get(name, d) }

# run a generated .bat / .sh in a terminal window the user can watch
fn plat_launch(path) {
    bind ext = str_lower(p_ext(path))
    if ext == "bat" || ext == "sh" || ext == "cmd" { return tm_sys_run(path) }
    fs_reveal(path)
    return true
}

# ── background jobs ───────────────────────────────────────────────────────────
# Preferred: the proc_* builtins (ling built/packed with the `web` feature) run a command
# silently with stdout+stderr appended to a log file. Fallback: a wrapper script started
# with tm_sys_run in a visible terminal; the wrapper appends ___LINGCODER_DONE___ <code>
# to the log so job polling can see the end. Probe once with try().
fn plat_probe_proc() { return proc_start("", [], "") }
fn plat_has_proc() {
    if st_has("has_proc") > 0.5 { return st_get("has_proc", 0.0) > 0.5 }
    bind r = try(plat_probe_proc)
    bind v = 0.0
    if r { bind v = 1.0 }
    st_set("has_proc", v)
    return v > 0.5
}

# start `exe` with list `argv` in `dir`; output appends to `log`. Returns a job handle
# (>= 1000 = proc id + 1000, 1 = wrapper script started, -1 = failed).
fn plat_job_start(exe, argv, dir, log) {
    plat_write(log, "")
    if plat_has_proc() {
        # proc_start has no working directory: wrap the command so `cd` happens inside
        bind id = 0.0 - 1.0
        if plat_os_name() == "windows" {
            bind full = ["/c", "cd /d \"" + str_replace(dir, "/", "\\") + "\" && \"" + str_replace(exe, "/", "\\") + "\""]
            bind i = 0.0
            while i < len(argv) {
                bind full = list_push(full, list_get(argv, i))
                bind i = i + 1.0
            }
            bind id = proc_start("cmd", full, log)
        } else {
            bind q = "cd \"" + dir + "\" && \"" + exe + "\""
            bind i = 0.0
            while i < len(argv) {
                bind q = q + " \"" + list_get(argv, i) + "\""
                bind i = i + 1.0
            }
            bind id = proc_start("sh", ["-c", q], log)
        }
        if id < 0.0 { return 0.0 - 1.0 }
        return id + 1000.0
    }
    # wrapper script fallback; the script names derive from the log name so two jobs
    # (a build and a Claude request, say) never overwrite each other's wrapper
    bind qargs = ""
    bind i = 0.0
    while i < len(argv) {
        bind qargs = qargs + " \"" + list_get(argv, i) + "\""
        bind i = i + 1.0
    }
    return plat_job_script("\"" + str_replace(exe, "/", "\\") + "\"" + qargs, "\"" + exe + "\"" + qargs, dir, log)
}

# run a raw command line (shell syntax) as a background job; same contract as plat_job_start
fn plat_job_shell(cmdline, dir, log) {
    plat_write(log, "")
    if plat_has_proc() {
        bind id = 0.0 - 1.0
        if plat_os_name() == "windows" {
            bind id = proc_start("cmd", ["/c", "cd /d \"" + str_replace(dir, "/", "\\") + "\" && " + cmdline], log)
        } else {
            bind id = proc_start("sh", ["-c", "cd \"" + dir + "\" && " + cmdline], log)
        }
        if id < 0.0 { return 0.0 - 1.0 }
        return id + 1000.0
    }
    return plat_job_script(cmdline, cmdline, dir, log)
}

# shared wrapper-script path: write <log-stem>_job.bat (+ a .vbs shim so no console
# window flashes) or <log-stem>_job.sh, appending output and the done marker to `log`
fn plat_job_script(wincmd, shcmd, dir, log) {
    bind stem = p_join(p_dir(log), p_stem(log) + "_job")
    if plat_os_name() == "windows" {
        bind sp = stem + ".bat"
        bind s = "@echo off\r\ncd /d \"" + str_replace(dir, "/", "\\") + "\"\r\n"
        bind s = s + wincmd + " >> \"" + str_replace(log, "/", "\\") + "\" 2>&1\r\n"
        bind s = s + "echo ___LINGCODER_DONE___ %errorlevel% >> \"" + str_replace(log, "/", "\\") + "\"\r\n"
        plat_write(sp, s)
        bind vp = stem + ".vbs"
        bind v = "CreateObject(\"WScript.Shell\").Run \"\"\"" + str_replace(sp, "/", "\\") + "\"\"\", 0, False\r\n"
        plat_write(vp, v)
        if tm_sys_run(vp) { return 1.0 }
        if tm_sys_run(sp) { return 1.0 }
        return 0.0 - 1.0
    }
    bind sp = stem + ".sh"
    bind s = "#!/usr/bin/env sh\ncd \"" + dir + "\"\n"
    bind s = s + shcmd + " >> \"" + log + "\" 2>&1\n"
    bind s = s + "echo \"___LINGCODER_DONE___ $?\" >> \"" + log + "\"\n"
    plat_write(sp, s)
    if tm_sys_run(sp) { return 1.0 }
    return 0.0 - 1.0
}

# -2 while running, else the exit code
fn plat_job_poll(handle, log) {
    if handle >= 1000.0 { return proc_poll(handle - 1000.0) }
    bind t = plat_read(log)
    bind at = str_find(t, "___LINGCODER_DONE___")
    if at < 0.0 { return 0.0 - 2.0 }
    bind rest = str_trim(str_slice(t, at + 21.0, 12.0))
    bind sp = str_find(rest, "\n")
    if sp >= 0.0 { bind rest = str_trim(str_slice(rest, 0.0, sp)) }
    return to_number(rest)
}
fn plat_job_kill(handle) {
    if handle >= 1000.0 { proc_kill(handle - 1000.0) }
    return 0.0
}
# the log text without the wrapper's end marker line
fn plat_job_log(log) {
    bind t = plat_read(log)
    bind at = str_find(t, "___LINGCODER_DONE___")
    if at >= 0.0 { bind t = str_slice(t, 0.0, at) }
    return t
}

← Back to index