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)
docs/EXTENSIONS.md
# Writing LingCoder extensions
An extension is one plain Ling file that LingCoder loads while it runs and reloads
every time you save it. No build step, no manifest, no restart.
## Where it lives
```
extensions/
myext/myext.ling <- the usual shape
quick.ling <- a flat file works too
```
The file name is the extension's **stem**. Every function in the file must start with
`<stem>_` - the loader refuses anything else, so an extension can never overwrite the
IDE's own functions. `Tools > New Extension...` writes a working one for you.
## The hooks LingCoder calls
Only `_info` is required; add the others as you need them.
| hook | you return | when it runs |
|---|---|---|
| `myext_info()` | `[title, description, version]` | at load; shown in the Extensions panel |
| `myext_init()` | anything | once after each (re)load |
| `myext_commands()` | `["id\|Label\|Shortcut\|icon", ...]` | at load; the entries appear in the Tools menu and the command palette (Ctrl+Shift+P) |
| `myext_run(id)` | a status message (or "") | when one of your commands is picked |
| `myext_panel_info()` | `[tooltip, icon]` | at load; registers an icon on the activity bar |
| `myext_panel(x, y, w, h)` | anything | every frame while your panel is the open sidebar |
| `myext_tick()` | anything | every frame (poll background jobs here) |
| `myext_on_event(ev, arg)` | anything | `ev` is `"open"`, `"save"` or `"project"`; `arg` is the path |
| `myext_ctx_edit()` | menu items like `"Label\|ext:myext:id\|\|icon"` | items added to the editor's right-click menu |
Errors inside any hook are caught and shown on your card in the Extensions panel
(Ctrl+Shift+X) - they never crash the IDE.
## The IDE API you can call
Everything the IDE itself uses is in scope, but this small surface is the stable one:
```
ide_status(msg) the status-bar line
ide_open(path) open a file in a tab; returns the buffer id or -1
ide_cur_path() path of the active file ("" when none)
ide_cur_text() its whole text
ide_selection() the selected text ("")
ide_cursor_line() the cursor's line (0-based)
ide_insert(text) insert at the cursor (replaces the selection)
ide_replace_selection(text) same as ide_insert
ide_goto(line) move the cursor to a line (0-based)
ide_project_root() the opened folder ("" when none)
ide_save_all() save every dirty tab
ide_problems_text() the lint findings, one per line
ide_run_action(a) run any IDE command, e.g. "file.save", "view.output"
side_show("ext:myext") open your own sidebar panel
ide_open_panel_tab("myext", "Title", "icon") your panel as a full editor tab
ide_close_panel_tab() close that tab again
```
For text input inside a panel use the editbox widget - a real multi-line input with
selection, cursor keys, word wrap, held-backspace and scrolling:
```
bind sent = u_editbox("myext_in", x, y, w, h, true, "the hint text")
# sent is true when Enter submitted it (Shift+Enter makes a new line)
eb_text("myext_in") eb_set("myext_in", s) eb_clear("myext_in")
```
Report long-running work with `st_set("ext_busy_myext", 1.0)` / `0.0` - the menu bar
shows a jobs chip while any extension is busy.
Also useful, straight from the IDE's toolkit: `st_get/st_set` (shared state),
`st_lpush/st_lget/...` (shared lists), the drawing helpers (`g_text`, `g_rect`,
`g_text_wrap`, `icon_draw`, `ds_word`), the widgets (`u_button`, `u_chip`,
`u_textfield`, `u_scroll_y`), the theme colours (`C_TEXT`, `C_BLUE`, `ds_col("mod")`,
...), paths (`p_join`, `p_base`), files (`plat_read`, `plat_write`), background jobs
(`plat_job_start/poll/kill/log`), and `json_escape`.
Keep your state keys prefixed too (`st_set("myext_count", ...)`) - the store is shared.
## A complete minimal extension
```
# shout.ling - upper-cases the selection.
fn shout_info() { return ["Shout", "Upper-case the selected text", "1.0"] }
fn shout_commands() { return ["go|Shout: Upper-case Selection||text"] }
fn shout_run(id) {
bind s = ide_selection()
if s == "" { return "Select some text first" }
ide_replace_selection(str_upper(s))
return "Shouted"
}
```
Save it as `extensions/shout/shout.ling` and it is live within two seconds.
## Drawing a panel
Your `myext_panel(x, y, w, h)` is handed the sidebar rectangle each frame and draws
immediate-mode, exactly like the IDE's own panels - look at
`extensions/sample/sample.ling` (a TODO list with scrolling and click-to-jump) and
`extensions/claude/claude.ling` (chat, background jobs, streaming) for working patterns.
## Background work
Never block a hook - the whole IDE is one frame loop. Start work with
`plat_job_start(exe, args, dir, log)`, poll it from `myext_tick()` with
`plat_job_poll`, read output with `plat_job_log`, and set
`st_set("ext_jobs", 1.0)` / `0.0` so the menu bar shows the busy chip.