灵符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/buffers.ling
# buffers.ling - open files. Each buffer keeps its lines in the st list "b<i>_lines" and its
# state in st keys b<i>_*. The tab order lives in the st list "buf_order"; "buf_cur" is the
# index (into buf_order) of the active buffer. Buffer ids only grow; closed ids are not reused.

fn buf_key(bi, k) { return "b" + num_str(bi) + "_" + k }
fn buf_path(bi) { return st_get(buf_key(bi, "path"), "") }
fn buf_lang(bi) { return st_get(buf_key(bi, "lang"), "plain") }
fn buf_dirty(bi) { return st_get(buf_key(bi, "dirty"), 0.0) > 0.5 }
fn buf_nlines(bi) { return st_llen(buf_key(bi, "lines")) }
fn buf_line(bi, i) {
    if i < 0.0 || i >= buf_nlines(bi) { return "" }
    return st_lget(buf_key(bi, "lines"), i)
}
fn buf_set_line(bi, i, s) { st_lset(buf_key(bi, "lines"), i, s) }
fn buf_insert_line(bi, i, s) { sl_insert(buf_key(bi, "lines"), i, s) }
fn buf_del_line(bi, i) { st_ldel(buf_key(bi, "lines"), i) }
fn buf_mark(bi) {
    st_set(buf_key(bi, "dirty"), 1.0)
    st_set(buf_key(bi, "lint_age"), st_get("now", 0.0))
}

fn buf_count() { return st_llen("buf_order") }
# the active buffer id, or -1
fn buf_cur() {
    bind n = buf_count()
    if n < 0.5 { return 0.0 - 1.0 }
    bind k = clampf(st_get("buf_cur", 0.0), 0.0, n - 1.0)
    return st_lget("buf_order", k)
}
fn buf_at(k) { return st_lget("buf_order", k) }
# position of buffer id in the tab order, or -1
fn buf_pos(bi) {
    bind i = 0.0
    while i < buf_count() {
        if buf_at(i) == bi { return i }
        bind i = i + 1.0
    }
    return 0.0 - 1.0
}
# buffer id already holding `path`, or -1
fn buf_find(path) {
    bind q = p_norm(path)
    bind i = 0.0
    while i < buf_count() {
        if buf_path(buf_at(i)) == q { return buf_at(i) }
        bind i = i + 1.0
    }
    return 0.0 - 1.0
}

fn buf_switch_to(bi) {
    bind k = buf_pos(bi)
    if k >= 0.0 {
        st_set("buf_cur", k)
        st_set("focus", "code")
        ed_sync_text()
    }
}

# whole text of a buffer
fn buf_text(bi) {
    bind s = ""
    bind n = buf_nlines(bi)
    bind i = 0.0
    while i < n {
        bind s = s + buf_line(bi, i)
        if i < n - 1.0 { bind s = s + "\n" }
        bind i = i + 1.0
    }
    return s
}

# open `path` in a buffer (or switch to it); returns the buffer id or -1
fn buf_open(path) {
    bind q = p_norm(path)
    bind have = buf_find(q)
    if have >= 0.0 {
        buf_switch_to(have)
        return have
    }
    if !file_exists(q) { status("Not found: " + q)  return 0.0 - 1.0 }
    if plat_file_size(q) > 2500000.0 { status("Too large to edit here (2.5 MB cap): " + p_base(q))  return 0.0 - 1.0 }
    bind text = plat_read(q)
    bind bi = st_get("buf_next", 0.0)
    st_set("buf_next", bi + 1.0)
    bind lines = str_split(str_replace(str_replace(text, "\r", ""), "\t", "    "), "\n")
    sl_from_list(buf_key(bi, "lines"), lines)
    if buf_nlines(bi) < 0.5 { st_lpush(buf_key(bi, "lines"), "") }
    st_set(buf_key(bi, "path"), q)
    st_set(buf_key(bi, "lang"), syn_lang(q))
    st_set(buf_key(bi, "dirty"), 0.0)
    st_set(buf_key(bi, "cl"), 0.0)
    st_set(buf_key(bi, "cc"), 0.0)
    st_set(buf_key(bi, "al"), 0.0 - 1.0)
    st_set(buf_key(bi, "ac"), 0.0)
    st_set(buf_key(bi, "scroll"), 0.0)
    st_set(buf_key(bi, "scrollx"), 0.0)
    st_set(buf_key(bi, "pref"), 0.0)
    st_lclear(buf_key(bi, "undo"))
    st_lclear(buf_key(bi, "redo"))
    st_lpush("buf_order", bi)
    st_set("buf_cur", buf_count() - 1.0)
    st_set("focus", "code")
    ed_sync_text()
    recent_file_add(q)
    if buf_lang(bi) == "ling" { lint_ling(bi) }
    return bi
}

fn buf_save(bi) {
    if bi < 0.0 { return false }
    bind path = buf_path(bi)
    if path == "" { return false }
    bind s = buf_text(bi) + "\n"
    if plat_write(path, s) {
        st_set(buf_key(bi, "dirty"), 0.0)
        status("Saved " + p_base(path))
        if buf_lang(bi) == "ling" { lint_ling(bi) }
        ext_event("save", path)
        return true
    }
    status("Could not write " + path)
    return false
}
fn buf_save_all() {
    bind i = 0.0
    while i < buf_count() {
        bind bi = buf_at(i)
        if buf_dirty(bi) { buf_save(bi) }
        bind i = i + 1.0
    }
}

# close the buffer at tab position k (no unsaved guard here - actions.ling guards)
fn buf_close_at(k) {
    bind bi = buf_at(k)
    st_lclear(buf_key(bi, "lines"))
    st_lclear(buf_key(bi, "undo"))
    st_lclear(buf_key(bi, "redo"))
    st_ldel("buf_order", k)
    if st_get("buf_cur", 0.0) >= buf_count() { st_set("buf_cur", max(0.0, buf_count() - 1.0)) }
    ed_sync_text()
}

# ── per-buffer undo (whole-text snapshots, like LingDev scenes) ───────────────
fn buf_snapshot(bi) {
    return num_str(st_get(buf_key(bi, "cl"), 0.0)) + "," + num_str(st_get(buf_key(bi, "cc"), 0.0)) + "\n" + buf_text(bi)
}
fn buf_restore(bi, snap) {
    bind nl = str_find(snap, "\n")
    bind head = str_split(str_slice(snap, 0.0, nl), ",")
    bind body = str_slice(snap, nl + 1.0, str_len(snap) - nl - 1.0)
    sl_from_list(buf_key(bi, "lines"), str_split(body, "\n"))
    if buf_nlines(bi) < 0.5 { st_lpush(buf_key(bi, "lines"), "") }
    bind l = clampf(to_number(list_get(head, 0)), 0.0, buf_nlines(bi) - 1.0)
    st_set(buf_key(bi, "cl"), l)
    st_set(buf_key(bi, "cc"), clampf(to_number(list_get(head, 1)), 0.0, str_len(buf_line(bi, l))))
    st_set(buf_key(bi, "al"), 0.0 - 1.0)
    buf_mark(bi)
    ed_sync_text()
}
# push an undo point; group rapid keystrokes into one (0.8 s window)
fn buf_undo_push(bi) {
    bind now = st_get("now", 0.0)
    if now - st_get(buf_key(bi, "undo_t"), 0.0 - 10.0) < 0.8 && st_llen(buf_key(bi, "undo")) > 0.5 {
        st_set(buf_key(bi, "undo_t"), now)
        return 0.0
    }
    st_set(buf_key(bi, "undo_t"), now)
    st_lpush(buf_key(bi, "undo"), buf_snapshot(bi))
    while st_llen(buf_key(bi, "undo")) > 80.0 { st_ldel(buf_key(bi, "undo"), 0.0) }
    st_lclear(buf_key(bi, "redo"))
    return 0.0
}
fn buf_undo(bi) {
    bind n = st_llen(buf_key(bi, "undo"))
    if n < 0.5 { status("Nothing to undo")  return 0.0 }
    st_lpush(buf_key(bi, "redo"), buf_snapshot(bi))
    buf_restore(bi, st_lget(buf_key(bi, "undo"), n - 1.0))
    st_ldel(buf_key(bi, "undo"), n - 1.0)
    st_set(buf_key(bi, "undo_t"), 0.0 - 10.0)
    return 0.0
}
fn buf_redo(bi) {
    bind n = st_llen(buf_key(bi, "redo"))
    if n < 0.5 { status("Nothing to redo")  return 0.0 }
    st_lpush(buf_key(bi, "undo"), buf_snapshot(bi))
    buf_restore(bi, st_lget(buf_key(bi, "redo"), n - 1.0))
    st_ldel(buf_key(bi, "redo"), n - 1.0)
    return 0.0
}

# recent files (for Ctrl+P ranking and the File menu)
fn recent_file_add(path) {
    bind keep = [path]
    bind i = 0.0
    while i < st_llen("recent_files") && len(keep) < 15.0 {
        bind r = st_lget("recent_files", i)
        if r != path { bind keep = list_push(keep, r) }
        bind i = i + 1.0
    }
    sl_from_list("recent_files", keep)
}

← Back to index