灵符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/app.ling
# app.ling - startup, the frame loop and the panel layout.

# plat_args() gives the whole command line (exe, "run", "--interp", the script, then ours):
# keep only what comes after the script path
fn app_user_args() {
    bind raw = plat_args()
    bind out = []
    bind past = false
    bind i = 1.0
    while i < len(raw) {
        bind a = list_get(raw, i)
        if past { bind out = list_push(out, a) }
        else if a != "run" && !str_starts_with(a, "--") { bind past = true }
        bind i = i + 1.0
    }
    return out
}

fn app_main(platform) {
    bind win_w = 1360.0
    bind win_h = 820.0
    bind args = app_user_args()
    bind i = 0.0
    while i < len(args) {
        if list_get(args, i) == "--size" && i + 1.0 < len(args) {
            bind p = str_split(list_get(args, i + 1.0), "x")
            if len(p) > 1.5 {
                bind win_w = max(700.0, to_number(list_get(p, 0)))
                bind win_h = max(480.0, to_number(list_get(p, 1)))
            }
        }
        bind i = i + 1.0
    }
    open_window(win_w, win_h, "LingCoder", 1.0)
    px_flat()
    st_set("app_root", app_find_root())
    bind f = font_load(p_join(app_root(), "assets/font.ttf"))
    if f < 0.0 { bind f = font_load("assets/font.ttf") }
    if f < 0.0 { bind f = font_load("../assets/font.ttf") }
    bind uf = font_load(p_join(app_root(), "assets/fonts/DaemonSans.ttf"))
    if uf < 0.0 { bind uf = font_load("assets/fonts/DaemonSans.ttf") }
    if f >= 0.0 && uf >= 0.0 { font_fallback(f, uf, 0.88) }
    st_set("font", f)
    # defaults (settings_load may override)
    st_set("side_w", 230.0)
    st_set("dock_h", 180.0)
    st_set("show_side", 1.0)
    st_set("show_dock", 0.0)
    st_set("dock_tab", 0.0)
    st_set("side_tab", "files")
    st_set("run_job", 0.0 - 1.0)
    st_set("status", "Ready")
    settings_load()
    ext_scan()
    # command line: a folder, files, and the test switches (--shot N --name X --drive "...")
    bind shot_at = 0.0 - 1.0
    bind opened = false
    bind i = 0.0
    while i < len(args) {
        bind a = list_get(args, i)
        if a == "--shot" && i + 1.0 < len(args) { bind shot_at = to_number(list_get(args, i + 1.0)) }
        if a == "--name" && i + 1.0 < len(args) { st_set("shot_name", list_get(args, i + 1.0)) }
        if a == "--side" && i + 1.0 < len(args) { side_show(list_get(args, i + 1.0)) }
        if a == "--dock" && i + 1.0 < len(args) {
            st_set("show_dock", 1.0)
            st_set("dock_tab", to_number(list_get(args, i + 1.0)))
        }
        if a == "--drive" && i + 1.0 < len(args) {
            st_set("fake_on", 1.0)
            bind evs = str_split(list_get(args, i + 1.0), ";")
            bind ei = 0.0
            while ei < len(evs) {
                bind f4 = str_split(list_get(evs, ei), ",")
                if len(f4) >= 4.0 {
                    st_lpush("dv_f", to_number(list_get(f4, 0)))
                    st_lpush("dv_x", to_number(list_get(f4, 1)))
                    st_lpush("dv_y", to_number(list_get(f4, 2)))
                    st_lpush("dv_d", to_number(list_get(f4, 3)))
                }
                bind ei = ei + 1.0
            }
        }
        if !str_starts_with(a, "--") && is_true(fs_is_dir(a)) { bind opened = project_open_path(a) }
        bind i = i + 1.0
    }
    # loose file arguments open as buffers
    bind i = 0.0
    while i < len(args) {
        bind a = list_get(args, i)
        if !str_starts_with(a, "--") && file_exists(a) && !is_true(fs_is_dir(a)) {
            if buf_open(a) >= 0.0 { bind opened = true }
        }
        bind i = i + 1.0
    }
    if !opened && st_get("load_last", 1.0) > 0.5 && st_llen("recent_proj") > 0.5 && is_true(fs_is_dir(st_lget("recent_proj", 0.0))) {
        project_open_path(st_lget("recent_proj", 0.0))
    }
    if shot_at < 0.5 { recover_check() }
    status("Welcome to LingCoder - Ctrl+Shift+P runs any command")
    bind frame = 0.0
    bind t_prev = time_now()
    # the runtime treats a held Escape as "close the window"; an IDE needs Esc for
    # closing palettes and menus, so the loop keeps running while Esc is the reason
    while (window_is_open() || key_down("escape")) && st_get("quit", 0.0) < 0.5 {
        st_set("frame", frame)
        px_theme(st_get("theme_black", 0.0))
        u_begin()
        bind overlay = st_get("menu_open", 0.0 - 1.0) >= 0.0 || st_get("ctx_open", 0.0) > 0.5 || st_get("modal", "") != "" || st_get("cb_open", "") != ""
        st_set("blk", sel(overlay, 1.0, 0.0))
        jobs_tick()
        # behind a dialog the frame is drawn once and reused (refreshed twice a second)
        bind mo = st_get("modal", "")
        bind cached = false
        if mo != "" && st_get("bg_modal", "") == mo && frame - st_get("bg_f", 0.0) < 30.0 && st_get("bg_w", 0.0) == get_width() && st_get("bg_h", 0.0) == get_height() {
            bind cached = px_snap_load() > 0.5
        }
        if !cached {
            draw_frame()
            st_set("blk", sel(st_get("ctx_open", 0.0) > 0.5 || st_get("modal", "") != "", 1.0, 0.0))
            menu_bar()
            draw_floating()
            if mo != "" {
                px_snap_save()
                st_set("bg_modal", mo)
                st_set("bg_f", frame)
                st_set("bg_w", get_width())
                st_set("bg_h", get_height())
            } else { st_set("bg_modal", "") }
        }
        st_set("blk", 0.0)
        bind act = menu_overlay()
        if act != "" { do_action(act) }
        modal_overlay()
        ctx_overlay()
        combo_overlay()
        if !overlay { keyboard() }
        u_end()
        present()
        bind now = time_now()
        st_set("frame_ms", (now - t_prev) * 1000.0)
        bind t_prev = now
        bind frame = frame + 1.0
        if shot_at > 0.0 && frame == shot_at {
            plat_screenshot(st_get("shot_name", "lingcoder"))
            st_set("quit", 1.0)
        }
    }
    if shot_at < 0.5 { autosave_now() }
    settings_save()
}

# background work, every frame
fn jobs_tick() {
    run_tick()
    ext_tick_all()
    autosave_tick()
    lint_tick()
}

# lint the active .ling buffer 0.8 s after the last change
fn lint_tick() {
    bind bi = buf_cur()
    if bi < 0.0 { return 0.0 }
    # non-ling buffers keep the problems of the last linted ling file
    if buf_lang(bi) != "ling" { return 0.0 }
    bind age = st_get(buf_key(bi, "lint_age"), 0.0)
    if age > 0.0 && st_get("now", 0.0) - age > 0.8 {
        st_set(buf_key(bi, "lint_age"), 0.0)
        lint_ling(bi)
    }
    # switching to a ling buffer whose problems are stale
    if st_get("problems_for", "") != buf_path(bi) && age == 0.0 {
        lint_ling(bi)
    }
    return 0.0
}

# ── layout ────────────────────────────────────────────────────────────────────
fn draw_frame() {
    bind W = get_width()
    bind H = get_height()
    g_rect(0.0, 0.0, W, H, C_BG_APP)
    bind top = MENU_H
    bind bottom = H - STATUS_H
    act_bar(top, bottom - top)
    bind left = ACT_W
    if st_get("show_side", 1.0) > 0.5 {
        bind sw = clampf(st_get("side_w", 230.0), 170.0, W * 0.4)
        side_panel(left, top, sw, bottom - top)
        bind hot = u_hot(left + sw - 3.0, top, 6.0, bottom - top)
        if hot && u_pressed() { u_set_active("side_resize") }
        if u_active() == "side_resize" && u_down() { st_set("side_w", clampf(u_mx() - left, 170.0, W * 0.4)) }
        if hot || u_active() == "side_resize" { g_rect(left + sw - 1.0, top, 2.0, bottom - top, C_SEL) }
        bind left = left + sw
    }
    bind w = W - left
    tabs_row(left, top, w)
    bind etop = top + TABS_H
    bind ebottom = bottom
    if st_get("show_dock", 1.0) > 0.5 {
        bind dh = clampf(st_get("dock_h", 180.0), 90.0, (bottom - etop) * 0.7)
        bind ebottom = bottom - dh
        dock_panel(left, ebottom, w, dh)
        bind hot = u_hot(left, ebottom - 3.0, w, 6.0)
        if hot && u_pressed() { u_set_active("dock_resize") }
        if u_active() == "dock_resize" && u_down() { st_set("dock_h", clampf(bottom - u_my(), 90.0, (bottom - etop) * 0.7)) }
        if hot || u_active() == "dock_resize" { g_rect(left, ebottom - 1.0, w, 2.0, C_SEL) }
    }
    ed_view(left, etop, w, ebottom - etop)
    status_bar()
    return 0.0
}

← Back to index