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/actions.ling
# actions.ling - every command behind the menus, the palette and the keyboard, plus the
# unsaved-changes flows. Action ids look like "file.save"; extension commands are
# "ext:<stem>:<id>" and go to the extension host.
fn buf_save_as(bi, path) {
if bi < 0.0 || path == "" { return false }
bind q = p_norm(path)
if plat_write(q, buf_text(bi) + "\n") {
st_set(buf_key(bi, "path"), q)
st_set(buf_key(bi, "lang"), syn_lang(q))
st_set(buf_key(bi, "dirty"), 0.0)
recent_file_add(q)
proj_files_invalidate()
status("Saved as " + p_base(q))
ext_event("save", q)
return true
}
status("Could not write " + q)
return false
}
# close tab k, with the unsaved guard
fn tab_close_ask(k) {
bind bi = buf_at(k)
if buf_dirty(bi) {
unsaved_ask(k, "tabx.close_go")
return 0.0
}
st_set("unsaved_k", k)
do_action("tabx.close_go")
return 0.0
}
# quitting: guard each dirty buffer in turn, then really quit
fn quit_request() {
bind i = 0.0
while i < buf_count() {
if buf_dirty(buf_at(i)) {
st_set("quit_pending", 1.0)
unsaved_ask(i, "file.quit")
return 0.0
}
bind i = i + 1.0
}
st_set("quit", 1.0)
return 0.0
}
fn do_action(a) {
if a == "" || a == "none" { return 0.0 }
bind bi = buf_cur()
# ── file ──
if a == "file.new" {
if proj_root() != "" { modal_text("New file in " + proj_name(), "newfile", "", proj_root()) }
else {
bind p = plat_dlg_save("New file", "ling,toml,md,txt", "untitled.ling", plat_home())
if p != "" {
plat_write(p, "")
buf_open(p)
}
}
return 0.0
}
if a == "file.open" {
bind p = plat_dlg_open("Open file", "", sel(proj_root() == "", plat_home(), proj_root()))
if p != "" { buf_open(p) }
return 0.0
}
if a == "file.open_folder" {
bind p = plat_dlg_folder("Open folder", sel(proj_root() == "", plat_home(), p_dir(proj_root())))
if p != "" { project_open_path(p) }
return 0.0
}
if str_starts_with(a, "recentp:") {
project_open_path(st_lget("recent_proj", to_number(str_slice(a, 8.0, str_len(a) - 8.0))))
return 0.0
}
if str_starts_with(a, "recentf:") {
buf_open(st_lget("recent_files", to_number(str_slice(a, 8.0, str_len(a) - 8.0))))
return 0.0
}
if a == "file.save" { buf_save(bi) return 0.0 }
if a == "file.save_as" {
if bi < 0.0 { return 0.0 }
bind p = plat_dlg_save("Save as", "", p_base(buf_path(bi)), p_dir(buf_path(bi)))
if p != "" { buf_save_as(bi, p) }
return 0.0
}
if a == "file.save_all" { buf_save_all() return 0.0 }
if a == "file.close_tab" {
if st_get("ptab_active", 0.0) > 0.5 { ide_close_panel_tab() }
else if buf_count() > 0.5 { tab_close_ask(st_get("buf_cur", 0.0)) }
return 0.0
}
if a == "file.close_folder" { project_close() return 0.0 }
if a == "file.settings" { modal_open("settings") return 0.0 }
if a == "file.quit" { quit_request() return 0.0 }
# ── edit ──
if a == "edit.undo" { if bi >= 0.0 { buf_undo(bi) } return 0.0 }
if a == "edit.redo" { if bi >= 0.0 { buf_redo(bi) } return 0.0 }
if a == "edit.cut" { if bi >= 0.0 { ed_cut(bi) } return 0.0 }
if a == "edit.copy" { if bi >= 0.0 { ed_copy(bi) } return 0.0 }
if a == "edit.paste" { if bi >= 0.0 { ed_paste(bi) } return 0.0 }
if a == "edit.select_all" { if bi >= 0.0 { ed_select_all(bi) } return 0.0 }
if a == "edit.find" { find_open(false) return 0.0 }
if a == "edit.replace" { find_open(true) return 0.0 }
if a == "edit.findfiles" { side_show("search") return 0.0 }
if a == "edit.comment" { if bi >= 0.0 { ed_toggle_comment(bi) } return 0.0 }
if a == "edit.dup_line" { if bi >= 0.0 { ed_dup_line(bi) } return 0.0 }
if a == "edit.line_up" { if bi >= 0.0 { ed_move_line(bi, 0.0 - 1.0) } return 0.0 }
if a == "edit.line_down" { if bi >= 0.0 { ed_move_line(bi, 1.0) } return 0.0 }
# ── view ──
if a == "view.files" { side_show("files") return 0.0 }
if a == "view.search" { side_show("search") return 0.0 }
if a == "view.ext" { side_show("ext") return 0.0 }
if a == "view.side" { st_set("show_side", 1.0 - st_get("show_side", 1.0)) return 0.0 }
if a == "view.dock" { st_set("show_dock", 1.0 - st_get("show_dock", 1.0)) return 0.0 }
if a == "view.problems" {
st_set("show_dock", 1.0)
st_set("dock_tab", 0.0)
return 0.0
}
if a == "view.output" {
st_set("show_dock", 1.0)
st_set("dock_tab", 1.0)
return 0.0
}
if a == "view.welcome" {
bind p = p_join(app_root(), "README.md")
if file_exists(p) { buf_open(p) } else { status("Welcome to LingCoder") }
return 0.0
}
if a == "text.bigger" { text_size_set(text_k() + 0.1) return 0.0 }
if a == "text.smaller" { text_size_set(text_k() - 0.1) return 0.0 }
if a == "text.reset" { text_size_set(1.0) return 0.0 }
# ── go ──
if a == "go.file" { pal_open("file") return 0.0 }
if a == "go.palette" { pal_open("cmd") return 0.0 }
if a == "go.line" {
if bi >= 0.0 { modal_text("Go to line (1-" + num_str(buf_nlines(bi)) + ")", "goto", "", "") }
return 0.0
}
if a == "go.next_tab" || a == "go.prev_tab" {
bind n = buf_count()
if n > 0.5 {
bind d = sel(a == "go.next_tab", 1.0, n - 1.0)
st_set("buf_cur", (st_get("buf_cur", 0.0) + d) % n)
st_set("focus", "code")
ed_sync_text()
}
return 0.0
}
if a == "go.next_problem" { problem_jump(1.0) return 0.0 }
if a == "go.prev_problem" { problem_jump(0.0 - 1.0) return 0.0 }
# ── run ──
if a == "run.file" { run_file() return 0.0 }
if a == "run.project" { run_project() return 0.0 }
if a == "run.build" { run_build() return 0.0 }
if a == "run.stop" { run_stop() return 0.0 }
if a == "run.clear" { run_clear() return 0.0 }
if a == "run.pick_exe" { modal_text("Path to ling.exe (empty = auto)", "ling_exe", st_get("ling_exe", ""), "") return 0.0 }
# ── tools / help ──
if a == "tools.ext_reload" { ext_reload_all() return 0.0 }
if a == "tools.ext_new" { modal_text("New extension name (letters, no spaces)", "ext_new", "myext", "") return 0.0 }
if a == "tools.ext_folder" { fs_reveal(ext_dir()) return 0.0 }
if a == "help.about" { modal_open("about") return 0.0 }
if a == "help.shortcuts" { modal_open("shortcuts") return 0.0 }
if a == "help.extapi" {
bind p = p_join(app_root(), "docs/EXTENSIONS.md")
if file_exists(p) { buf_open(p) } else { status("docs/EXTENSIONS.md is missing") }
return 0.0
}
# ── tab context menu ──
if a == "tabx.close" { tab_close_ask(st_get("ctx_tab", 0.0)) return 0.0 }
if a == "tabx.close_go" {
bind k = st_get("unsaved_k", 0.0)
if k < buf_count() { buf_close_at(k) }
if st_get("quit_pending", 0.0) > 0.5 { quit_request() }
return 0.0
}
if a == "tabx.others" {
bind keep = buf_at(st_get("ctx_tab", 0.0))
bind guard = 0.0
while buf_count() > 1.0 && guard < 200.0 {
bind k = sel(buf_at(0.0) == keep, 1.0, 0.0)
if buf_dirty(buf_at(k)) { unsaved_ask(k, "tabx.others") return 0.0 }
buf_close_at(k)
bind guard = guard + 1.0
}
st_set("buf_cur", 0.0)
ed_sync_text()
return 0.0
}
if a == "tabx.all" {
bind guard = 0.0
while buf_count() > 0.5 && guard < 200.0 {
if buf_dirty(buf_at(0.0)) { unsaved_ask(0.0, "tabx.all") return 0.0 }
buf_close_at(0.0)
bind guard = guard + 1.0
}
return 0.0
}
if a == "tabx.copy_path" {
plat_clip_set(buf_path(buf_at(st_get("ctx_tab", 0.0))))
status("Path copied")
return 0.0
}
if a == "tabx.reveal" { plat_reveal(buf_path(buf_at(st_get("ctx_tab", 0.0)))) return 0.0 }
# ── app ──
if a == "app.recover_go" { recover_go() return 0.0 }
if a == "app.quit" { quit_request() return 0.0 }
# ── extension commands ──
if str_starts_with(a, "ext:") { ext_run_action(a) return 0.0 }
status("Unknown command: " + a)
return 0.0
}
# the explorer / tab context menus land here (ctx_kind + ctx_arg are set)
fn do_ctx_action(a) {
bind kind = st_get("ctx_kind", "")
bind arg = st_get("ctx_arg", "")
if kind == "edit" { do_action(a) return 0.0 }
if kind == "tab" { do_action(a) return 0.0 }
if a == "fx.open" { buf_open(arg) return 0.0 }
if a == "fx.run" {
buf_open(arg)
run_file()
return 0.0
}
if a == "fx.new_file" {
bind dir = sel(kind == "file", p_dir(arg), arg)
modal_text("New file in " + p_base(dir), "newfile", "", dir)
return 0.0
}
if a == "fx.new_folder" {
bind dir = sel(kind == "file", p_dir(arg), arg)
modal_text("New folder in " + p_base(dir), "newfolder", "", dir)
return 0.0
}
if a == "fx.rename" {
modal_text("Rename " + p_base(arg), "rename", p_base(arg), arg)
return 0.0
}
if a == "fx.dup" {
bind e = p_ext(arg)
bind stem = p_stem(arg)
bind dst = p_join(p_dir(arg), stem + " copy" + sel(e == "", "", "." + e))
plat_copy(arg, dst)
dir_invalidate(p_dir(arg))
proj_files_invalidate()
return 0.0
}
if a == "fx.copy_path" {
plat_clip_set(arg)
status("Path copied")
return 0.0
}
if a == "fx.reveal" { plat_reveal(arg) return 0.0 }
if a == "fx.refresh" {
st_set("xp_flush", st_get("now", 0.0))
dir_invalidate(arg)
proj_files_invalidate()
return 0.0
}
if a == "fx.delete" {
st_set("confirm_path", arg)
confirm_open("Delete", "Permanently delete " + p_base(arg) + sel(is_true(fs_is_dir(arg)), " and everything in it?", " from disk?"), "fx.delete_go")
return 0.0
}
if a == "fx.delete_go" {
file_delete_go(st_get("confirm_path", ""))
dir_invalidate(p_dir(st_get("confirm_path", "")))
return 0.0
}
do_action(a)
return 0.0
}
# ── the keyboard (no overlay open) ────────────────────────────────────────────
fn keyboard() {
bind ctrl = u_ctrl()
bind shift = u_shift()
bind alt = u_alt()
bind focus = st_get("focus", "")
bind intext = focus != "" && focus != "code"
if ctrl {
if key_pressed("n") { do_action("file.new") }
if key_pressed("o") { do_action(sel(shift, "file.open_folder", "file.open")) }
if key_pressed("s") {
if alt { do_action("file.save_all") } else { do_action("file.save") }
}
if key_pressed("w") { do_action("file.close_tab") }
if key_pressed("q") { do_action("file.quit") }
if key_pressed("comma") { do_action("file.settings") }
if key_pressed("p") { do_action(sel(shift, "go.palette", "go.file")) }
if key_pressed("g") { do_action("go.line") }
if !intext {
if key_pressed("f") { do_action(sel(shift, "edit.findfiles", "edit.find")) }
if key_pressed("h") { do_action("edit.replace") }
}
if key_pressed("j") { do_action("view.dock") }
if shift && key_pressed("e") { do_action("view.files") }
if shift && key_pressed("x") { do_action("view.ext") }
if shift && key_pressed("m") { do_action("view.problems") }
if shift && key_pressed("u") { do_action("view.output") }
if key_pressed("k") { ext_claude_quick() }
if key_pressed("f5") { do_action("run.project") }
if shift && (key_pressed("=") || key_pressed("equals") || key_pressed("numpad+")) { text_size_set(text_k() + 0.1) }
if shift && (key_pressed("-") || key_pressed("minus") || key_pressed("numpad-")) { text_size_set(text_k() - 0.1) }
if key_pressed("0") || key_pressed("numpad0") { text_size_set(1.0) }
if key_pressed("pagedown") { do_action("go.next_tab") }
if key_pressed("pageup") { do_action("go.prev_tab") }
} else {
if key_pressed("f1") { do_action("help.shortcuts") }
if key_pressed("f3") && buf_cur() >= 0.0 { ed_find_next(buf_cur(), sel(shift, 0.0 - 1.0, 1.0)) }
if key_pressed("f5") { do_action(sel(shift, "run.stop", "run.file")) }
if key_pressed("f7") { do_action("run.build") }
if key_pressed("f8") { do_action(sel(shift, "go.prev_problem", "go.next_problem")) }
if key_pressed("f12") { do_action("view.side") }
}
if focus == "code" && buf_cur() >= 0.0 { ed_keys(buf_cur()) }
return 0.0
}