灵符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/palette.ling
# palette.ling - the command palette (Ctrl+Shift+P) and Go to File (Ctrl+P). One modal,
# two modes (st "pal_mode"): "cmd" lists every menu command (+ extension commands);
# "file" lists the project files and the open buffers. In file mode ":12" or "name:12"
# jumps to a line.

fn pal_build_cmds() {
    bind out = []
    bind seen = "|"
    bind titles = menu_titles()
    bind m = 0.0
    while m < len(titles) {
        bind items = menu_def(m)
        bind i = 0.0
        while i < len(items) {
            bind it = list_get(items, i)
            if it != "-" {
                bind act = menu_field(it, 1)
                if !str_starts_with(act, "sub:") && act != "none" && str_find(seen, "|" + act + "|") < 0.0 {
                    bind out = list_push(out, menu_field(it, 0) + "|" + act + "|" + menu_field(it, 2) + "|" + menu_field(it, 3) + "|" + list_get(titles, m))
                    bind seen = seen + act + "|"
                }
            }
            bind i = i + 1.0
        }
        bind m = m + 1.0
    }
    return out
}

fn pal_build_files() {
    bind out = []
    # open buffers first
    bind i = 0.0
    while i < buf_count() {
        bind p = buf_path(buf_at(i))
        bind out = list_push(out, p_base(p) + "|open:" + num_str(buf_at(i)) + "|open|" + ext_icon(p_ext(p)) + "|" + p_rel(proj_root(), p))
        bind i = i + 1.0
    }
    bind n = proj_files()
    bind k = 0.0
    while k < n {
        bind rel = st_lget("proj_files", k)
        bind out = list_push(out, p_base(rel) + "|file:" + rel + "||" + ext_icon(p_ext(rel)) + "|" + rel)
        bind k = k + 1.0
    }
    return out
}

fn pal_open(mode) {
    st_set("pal_mode", mode)
    if mode == "cmd" { st_set("pal_all", pal_build_cmds()) }
    else { st_set("pal_all", pal_build_files()) }
    st_set("pal_q", "")
    st_set("pal_q_last", "-")
    st_set("pal_sel", 0.0)
    st_set("pal_off", 0.0)
    st_set("menu_open", 0.0 - 1.0)
    modal_open("palette")
    st_set("focus", "pal_q")
    st_set("text_owner", "")
}

# fuzzy score: substring beats word start beats scattered letters; -1 = no match
fn pal_score(q, t) {
    if q == "" { return 0.0 }
    bind l = str_lower(t)
    bind at = str_find(l, q)
    if at >= 0.0 {
        bind word = true
        if at > 0.5 { bind word = str_slice(l, at - 1.0, 1.0) == " " || str_slice(l, at - 1.0, 1.0) == "/" }
        return 1000.0 - at + sel(word, 200.0, 0.0)
    }
    bind ws = str_split(q, " ")
    if len(ws) > 1.5 {
        bind all = true
        bind wi = 0.0
        while wi < len(ws) {
            if list_get(ws, wi) != "" && str_find(l, list_get(ws, wi)) < 0.0 { bind all = false }
            bind wi = wi + 1.0
        }
        if all { return 600.0 }
    }
    bind qi = 0.0
    bind ti = 0.0
    bind gaps = 0.0
    bind last = 0.0 - 1.0
    bind nq = str_len(q)
    bind nt = str_len(l)
    while qi < nq && ti < nt {
        if str_slice(l, ti, 1.0) == str_slice(q, qi, 1.0) {
            if last >= 0.0 { bind gaps = gaps + (ti - last - 1.0) }
            bind last = ti
            bind qi = qi + 1.0
        }
        bind ti = ti + 1.0
    }
    if qi < nq { return 0.0 - 1.0 }
    return max(1.0, 300.0 - gaps * 4.0)
}

fn pal_list_insert(l, pos, v) {
    bind out = []
    bind i = 0.0
    while i < len(l) {
        if i == pos { bind out = list_push(out, v) }
        bind out = list_push(out, list_get(l, i))
        bind i = i + 1.0
    }
    if pos >= len(l) { bind out = list_push(out, v) }
    return out
}

fn pal_filter(q) {
    bind all = st_get("pal_all", [])
    bind n = len(all)
    bind idx = []
    bind sc = []
    bind recent = st_get("pal_recent", "")
    bind i = 0.0
    while i < n {
        bind it = list_get(all, i)
        bind s = pal_score(q, menu_field(it, 0) + "  " + menu_field(it, 4))
        if q == "" {
            bind ra = str_find(recent, "|" + menu_field(it, 1) + "|")
            bind s = sel(ra >= 0.0, 5000.0 - ra, 0.0 - i * 0.001)
        }
        if s > 0.0 - 0.5 || q == "" {
            bind pos = len(idx)
            bind more = pos > 0.5
            while more {
                if list_get(sc, pos - 1.0) < s { bind pos = pos - 1.0 } else { bind more = false }
                if pos < 0.5 { bind more = false }
            }
            bind idx = pal_list_insert(idx, pos, i)
            bind sc = pal_list_insert(sc, pos, s)
        }
        bind i = i + 1.0
    }
    return idx
}

fn pal_run(act, lineafter) {
    modal_close()
    st_set("pal_recent", "|" + act + str_replace(st_get("pal_recent", "|"), "|" + act + "|", "|"))
    if str_starts_with(act, "open:") {
        buf_switch_to(to_number(str_slice(act, 5.0, str_len(act) - 5.0)))
        if lineafter >= 0.0 { ed_goto_line(buf_cur(), lineafter) }
        return 0.0
    }
    if str_starts_with(act, "file:") {
        bind bi = buf_open(proj_abs(str_slice(act, 5.0, str_len(act) - 5.0)))
        if bi >= 0.0 && lineafter >= 0.0 { ed_goto_line(bi, lineafter) }
        return 0.0
    }
    do_action(act)
    return 0.0
}

fn modal_palette() {
    bind W = get_width()
    bind H = get_height()
    bind w = min(620.0, W - 40.0)
    bind rh = 28.0
    bind rows = clampf(floor((H - 200.0) / rh), 4.0, 12.0)
    bind h = 58.0 + rows * rh + 28.0
    bind x = floor((W - w) * 0.5)
    bind y = 56.0
    g_rect_a(0.0, 0.0, W, H, 0.0, 0.45)
    px_rrect(x + 3.0, y + 5.0, w, h, 8.0, 0.0, 0.5)
    g_rrect(x, y, w, h, 7.0, C_POPUP)
    g_rframe(x, y, w, h, 7.0, C_POPUP_LINE)
    bind mode = st_get("pal_mode", "cmd")
    icon_draw(sel(mode == "cmd", "terminal", "search"), x + 22.0, y + 25.0, 13.0, C_TEXT_DIM)
    bind q = u_textfield("pal_q", x + 38.0, y + 12.0, w - 50.0, 28.0, st_get("pal_q", ""))
    st_set("pal_q", q)
    st_set("focus", "pal_q")
    if q == "" {
        g_text(x + 46.0, y + 19.0, 12.0, C_TEXT_DIM, sel(mode == "cmd", "Type a command: save, run, find, claude ...", "Type a file name (add :12 to jump to a line)"))
    }
    # a trailing :NN in file mode jumps to that line
    bind lineafter = 0.0 - 1.0
    bind ql = str_lower(str_trim(q))
    if mode == "file" {
        bind cp = str_find(ql, ":")
        if cp >= 0.0 {
            bind lineafter = to_number(str_slice(ql, cp + 1.0, str_len(ql) - cp - 1.0)) - 1.0
            bind ql = str_trim(str_slice(ql, 0.0, cp))
            if ql == "" && lineafter >= 0.0 {
                # ":12" alone: jump in the current file
                g_text(x + 46.0, y + 19.0, 12.0, C_TEXT_DIM, "line " + num_str(lineafter + 1.0) + " of the current file - Enter jumps")
            }
        }
    }
    if ql != st_get("pal_q_last", "-") {
        st_set("pal_res", pal_filter(ql))
        st_set("pal_q_last", ql)
        st_set("pal_sel", 0.0)
        st_set("pal_off", 0.0)
    }
    bind res = st_get("pal_res", [])
    bind all = st_get("pal_all", [])
    bind n = len(res)
    bind cur = clampf(st_get("pal_sel", 0.0), 0.0, max(0.0, n - 1.0))
    if key_pressed("down") { bind cur = min(n - 1.0, cur + 1.0) }
    if key_pressed("up") { bind cur = max(0.0, cur - 1.0) }
    if key_pressed("pagedown") { bind cur = min(n - 1.0, cur + rows) }
    if key_pressed("pageup") { bind cur = max(0.0, cur - rows) }
    bind off = st_get("pal_off", 0.0)
    if u_in(x, y + 50.0, w, rows * rh) && u_scroll() != 0.0 {
        bind off = clampf(off - u_scroll() * 3.0, 0.0, max(0.0, n - rows))
    }
    if cur < off { bind off = cur }
    if cur >= off + rows { bind off = cur - rows + 1.0 }
    bind off = clampf(off, 0.0, max(0.0, n - rows))
    bind ly = y + 50.0
    u_hsep(x + 8.0, ly - 2.0, w - 16.0)
    bind run = ""
    bind r = 0.0
    while r < rows {
        bind k = off + r
        if k < n {
            bind it = list_get(all, list_get(res, k))
            bind ry = ly + r * rh
            bind hot = u_hot(x + 4.0, ry, w - 8.0, rh)
            if hot && (u_mx() != st_get("pal_mx", 0.0) || u_my() != st_get("pal_my", 0.0)) { bind cur = k }
            if k == cur {
                g_rrect_a(x + 6.0, ry + 1.0, w - 12.0, rh - 2.0, 4.0, C_BLUE, 0.28)
                g_rect(x + 6.0, ry + 4.0, 3.0, rh - 8.0, C_BLUE)
            }
            bind ic = menu_field(it, 3)
            if ic != "" { icon_draw(ic, x + 26.0, ry + rh * 0.5, 12.0, sel(k == cur, C_TEXT_STRONG, C_TEXT_DIM)) }
            bind sc = menu_field(it, 2)
            bind scw = sel(sc == "", 0.0, g_text_w(11.0, sc) + 16.0)
            bind lab = menu_field(it, 0)
            bind lw = g_text_w(13.0, lab)
            bind room = w - 60.0 - scw - 8.0
            g_text_fit(x + 44.0, ry + 7.0, room, 13.0, sel(k == cur, C_TEXT_STRONG, C_TEXT), lab)
            if lw + 20.0 < room { g_text_fit(x + 44.0 + lw + 12.0, ry + 8.0, room - lw - 12.0, 11.0, C_TEXT_DIM, menu_field(it, 4)) }
            if sc != "" {
                bind sx = x + w - 12.0 - scw + 4.0
                g_rrect(sx, ry + 5.0, scw - 6.0, rh - 10.0, 3.0, C_BG_DEEP)
                g_text_c(sx + (scw - 6.0) * 0.5, ry + 8.0, 11.0, C_TEXT, sc)
            }
            if hot && u_pressed() { bind run = menu_field(it, 1) }
        }
        bind r = r + 1.0
    }
    st_set("pal_mx", u_mx())
    st_set("pal_my", u_my())
    if n < 0.5 { g_text_c(x + w * 0.5, ly + 16.0, 12.0, C_TEXT_DIM, sel(mode == "cmd", "No command matches", "No file matches")) }
    st_set("pal_sel", cur)
    st_set("pal_off", off)
    g_text(x + 14.0, y + h - 22.0, 11.0, C_TEXT_DIM, "Up / Down choose    Enter run    Esc close")
    g_text_r(x + w - 14.0, y + h - 22.0, 11.0, C_TEXT_DIM, num_str(n) + sel(mode == "cmd", " commands", " files"))
    if key_pressed("enter") {
        if mode == "file" && str_trim(str_lower(q)) != "" && str_starts_with(str_trim(str_lower(q)), ":") && lineafter >= 0.0 {
            modal_close()
            ed_goto_line(buf_cur(), lineafter)
            return 0.0
        }
        if n > 0.5 { bind run = menu_field(list_get(all, list_get(res, cur)), 1) }
    }
    if u_pressed() && !u_in(x, y, w, h) { modal_close() }
    if run != "" { pal_run(run, lineafter) }
    return 0.0
}

← Back to index