L
lingcoder
↓ 0 downloadsLingCoder: 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 lingcoderCode / 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)
}