灵符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)

extensions/miao/miao.ling
# miao.ling - the miao (temple of versions) panel for LingCoder: repo stats at a glance
# and every ceremony one click away, in the sidebar or as a full tab. Works in any
# folder that holds a .miao temple ("and other projects too"): the miao program itself
# is found next door in ../ling-source (override with miao_home= in settings.txt).
# The Claude extension asks this one where miao lives, so `miao ...` commands Claude
# runs are routed here too.

fn miao_info() { return ["Miao - temple of versions", "Version control for Ling projects: divine, offer, rite, lineage ... - never git", "1.0"] }
fn miao_panel_info() { return ["Miao - the temple of versions (repo panel)", "home"] }
fn miao_commands() {
    return ["panel|Miao: Open Panel||home", "tab|Miao: Open as Tab||window", "divine|Miao: Divine (status)||eye", "offer_all|Miao: Offer All (stage .)||plus", "rite|Miao: Rite (commit)...||save", "lineage|Miao: Lineage (log)||bars", "yinyang|Miao: YinYang (diff)||layers", "pilgrimage|Miao: Pilgrimage (pull)||arrow_down", "tribute|Miao: Tribute (push)||arrow_up", "chronicle|Miao: Chronicle (reflog)||clock"]
}

# ── where things are ──────────────────────────────────────────────────────────
fn miao_home() {
    bind s = str_trim(st_get("miao_home", ""))
    if s != "" && file_exists(p_join(s, "ling/rites.ling")) { return p_norm(s) }
    bind near = p_join(p_dir(app_root()), "ling-source")
    if file_exists(p_join(near, "ling/rites.ling")) { return near }
    return ""
}
# the temple root at or above the open folder ("" when none)
fn miao_repo_root() {
    bind start = proj_root()
    if start == "" && ide_cur_path() != "" { bind start = p_dir(ide_cur_path()) }
    if start == "" { return "" }
    if st_get("mi_root_for", "") == start && st_get("now", 0.0) - st_get("mi_root_t", 0.0 - 10.0) < 3.0 { return st_get("mi_root", "") }
    bind d = p_norm(start)
    bind found = ""
    bind k = 0.0
    while k < 8.0 && d != "" && found == "" {
        if is_true(fs_is_dir(p_join(d, ".miao"))) { bind found = d }
        bind up = fs_parent(d)
        if p_norm(up) == d { bind k = 8.0 }
        bind d = p_norm(up)
        bind k = k + 1.0
    }
    st_set("mi_root_for", start)
    st_set("mi_root", found)
    st_set("mi_root_t", st_get("now", 0.0))
    return found
}
fn miao_dir() { return p_join(miao_repo_root(), ".miao") }

fn miao_run(id) {
    if id == "panel" { side_show("ext:miao")  return "" }
    if id == "tab" { ide_open_panel_tab("miao", "Miao Temple", "home")  return "" }
    if id == "divine" { miao_exec(["divine"], "divine")  return "" }
    if id == "offer_all" { miao_exec(["offer", "."], "offer .")  return "" }
    if id == "rite" {
        side_show("ext:miao")
        st_set("focus", "mi_msg")
        st_set("text_owner", "")
        return "Type the rite message in the Miao panel, then press Rite"
    }
    if id == "lineage" { miao_exec(["lineage", "-n", "15"], "lineage")  return "" }
    if id == "yinyang" { miao_exec(["yinyang"], "yinyang")  return "" }
    if id == "pilgrimage" { miao_exec(["pilgrimage"], "pilgrimage")  return "" }
    if id == "tribute" { miao_exec(["tribute"], "tribute")  return "" }
    if id == "chronicle" { miao_exec(["chronicle"], "chronicle")  return "" }
    return ""
}

# ── running ceremonies ────────────────────────────────────────────────────────
fn miao_busy() { return st_get("mi_job", 0.0 - 1.0) >= 0.0 }
fn miao_log() { return p_join(cache_dir(), "miao.log") }
fn miao_exec(args, label) {
    if miao_busy() { ide_status("A miao ceremony is already running")  return 0.0 }
    bind home = miao_home()
    if home == "" { ide_status("miao was not found - set miao_home= in settings.txt")  return 0.0 }
    bind argv = ["run", "--interp", p_join(home, "ling/main.ling")]
    bind i = 0.0
    while i < len(args) {
        bind argv = list_push(argv, list_get(args, i))
        bind i = i + 1.0
    }
    bind argv = list_push(argv, "--plain")
    bind dir = miao_repo_root()
    if dir == "" { bind dir = sel(proj_root() == "", app_root(), proj_root()) }
    bind h = plat_job_start(ling_exe_path(), argv, dir, miao_log())
    if h < 0.0 { ide_status("Could not start miao")  return 0.0 }
    st_set("mi_job", h)
    st_set("mi_label", label)
    st_set("mi_out", "$ miao " + label + "\n")
    st_set("mi_t0", st_get("now", 0.0))
    st_set("ext_busy_miao", 1.0)
    st_set("mi_off", 0.0 - 1.0)
    ide_status("miao " + label + " ...")
    return 0.0
}
fn miao_tick() {
    if !miao_busy() { return 0.0 }
    if st_get("now", 0.0) - st_get("mi_poll", 0.0) < 0.3 { return 0.0 }
    st_set("mi_poll", st_get("now", 0.0))
    bind t = plat_job_log(miao_log())
    st_set("mi_out", "$ miao " + st_get("mi_label", "") + "\n" + t)
    bind code = plat_job_poll(st_get("mi_job", 0.0 - 1.0), miao_log())
    if code > 0.0 - 1.5 {
        st_set("mi_job", 0.0 - 1.0)
        st_set("ext_busy_miao", 0.0)
        st_set("mi_stat_t", 0.0 - 10.0)
        dir_invalidate(miao_repo_root())
        ide_status("miao " + st_get("mi_label", "") + sel(code == 0.0, " done", " failed (exit " + num_str(code) + ")"))
    }
    return 0.0
}

# ── temple stats straight from .miao (cached 2 s) ─────────────────────────────
fn miao_stats() {
    if st_get("now", 0.0) - st_get("mi_stat_t", 0.0 - 10.0) < 2.0 { return 0.0 }
    st_set("mi_stat_t", st_get("now", 0.0))
    bind d = miao_dir()
    bind soul = str_trim(str_replace(plat_read(p_join(d, "SOUL")), "soul:", ""))
    st_set("mi_soul", sel(soul == "", "main", soul))
    bind items = fs_list(p_join(d, "souls"))
    bind names = ""
    bind i = 0.0
    while i < len(items) / 3.0 {
        bind names = names + sel(names == "", "", ", ") + list_get(items, i * 3.0)
        bind i = i + 1.0
    }
    st_set("mi_souls", names)
    st_set("mi_nsouls", len(items) / 3.0)
    st_set("mi_ntal", len(fs_list(p_join(d, "talismans"))) / 3.0)
    bind cfg = plat_read(p_join(d, "config"))
    st_set("mi_keeper", str_trim(cfg_get(str_replace(cfg, "|", "="), "author", "(unnamed)")))
    bind altar = str_trim(plat_read(p_join(d, "altar")))
    bind staged = 0.0
    if altar != "" { bind staged = len(str_split(altar, "\n")) }
    st_set("mi_staged", staged)
    bind head = str_trim(plat_read(p_join(d, "souls/" + st_get("mi_soul", "main"))))
    st_set("mi_head", sel(head == "", "(no rites yet)", str_slice(head, 0.0, 8.0)))
    bind chron = str_trim(plat_read(p_join(d, "chronicle")))
    bind nrites = 0.0
    bind last = ""
    if chron != "" {
        bind lines = str_split(chron, "\n")
        bind k = 0.0
        while k < len(lines) {
            bind parts = str_split(list_get(lines, k), "|")
            if len(parts) > 1.5 && list_get(parts, 1) == "rite" { bind nrites = nrites + 1.0 }
            bind k = k + 1.0
        }
        bind lparts = str_split(list_get(lines, len(lines) - 1.0), "|")
        if len(lparts) > 1.5 { bind last = list_get(lparts, 1) }
    }
    st_set("mi_nrites", nrites)
    st_set("mi_last", last)
    return 0.0
}

fn miao_kv(x, y, w, k, v) {
    g_text(x, y, 11.0, C_TEXT_DIM, k)
    g_text_r(x + w, y, 11.0, C_TEXT_STRONG, g_fit(11.0, w * 0.62, v))
    return 0.0
}
fn miao_btn(id, x, y, w, label, alias, col) {
    bind r = u_button(id, x, y, w, 22.0, label, col)
    if u_hot(x, y, w, 22.0) { u_tip("miao " + str_lower(label) + "  (git: " + alias + ")") }
    return r
}

# ── the panel ─────────────────────────────────────────────────────────────────
fn miao_panel(px, py, pw, ph) {
    bind wide = pw > 700.0
    bind x = px
    bind w = sel(wide, 280.0, pw)
    ds_word(x + 10.0, py + 10.0, 10.0, "MIAO", ds_col("asm"), false)
    g_text(x + 64.0, py + 11.0, 10.0, C_TEXT_DIM, "庙 - temple of versions")
    if st_get("ptab_active", 0.0) < 0.5 || st_get("ptab_stem", "") != "miao" {
        if u_icon_button("mi_totab", x + w - 26.0, py + 4.0, 20.0, 18.0, "window", 10.0, C_TEXT_DIM, false) { miao_run("tab") }
        if u_hot(x + w - 26.0, py + 4.0, 20.0, 18.0) { u_tip("Open the temple as a full tab") }
    }
    bind y = py + 30.0
    if miao_home() == "" {
        g_text_wrap(x + 12.0, y + 6.0, w - 24.0, 11.0, ds_col("bind"), "The miao program was not found. It usually lives in ../ling-source; set miao_home=<folder> in settings.txt if yours is elsewhere.")
        return 0.0
    }
    bind root = miao_repo_root()
    if root == "" {
        g_text_wrap(x + 12.0, y + 6.0, w - 24.0, 11.0, C_TEXT_DIM, "No temple stands here" + sel(proj_root() == "", " - open a folder first (Ctrl+Shift+O).", "."))
        if proj_root() != "" {
            if u_button("mi_consecrate", x + 12.0, y + 54.0, w - 24.0, 26.0, "Consecrate this folder  (init)", C_SEL) { miao_exec(["consecrate", proj_root()], "consecrate") }
        }
        miao_out_view(sel(wide, px + w + 8.0, x), sel(wide, py + 4.0, y + 92.0), sel(wide, pw - w - 16.0, w - 8.0), sel(wide, ph - 8.0, ph - (y + 92.0 - py) - 4.0))
        return 0.0
    }
    miao_stats()
    # stats
    miao_kv(x + 12.0, y, w - 24.0, "temple", p_base(root))
    miao_kv(x + 12.0, y + 16.0, w - 24.0, "soul (branch)", st_get("mi_soul", "main") + sel(st_get("mi_nsouls", 1.0) > 1.0, "  (of " + num_str(st_get("mi_nsouls", 1.0)) + ")", ""))
    miao_kv(x + 12.0, y + 32.0, w - 24.0, "head rite", st_get("mi_head", ""))
    miao_kv(x + 12.0, y + 48.0, w - 24.0, "rites / talismans", num_str(st_get("mi_nrites", 0.0)) + " / " + num_str(st_get("mi_ntal", 0.0)))
    miao_kv(x + 12.0, y + 64.0, w - 24.0, "on the altar", num_str(st_get("mi_staged", 0.0)) + " offering(s)")
    miao_kv(x + 12.0, y + 80.0, w - 24.0, "keeper", st_get("mi_keeper", ""))
    bind y = y + 100.0
    u_hsep(x + 8.0, y, w - 16.0)
    bind y = y + 8.0
    # everyday ceremonies
    bind bw = floor((w - 28.0) / 2.0)
    if miao_btn("mi_divine", x + 10.0, y, bw, "Divine", "status", C_SEL) { miao_exec(["divine"], "divine") }
    if miao_btn("mi_offer", x + 14.0 + bw, y, bw, "Offer all", "add .", 0.0) { miao_exec(["offer", "."], "offer .") }
    bind y = y + 26.0
    if miao_btn("mi_lineage", x + 10.0, y, bw, "Lineage", "log", 0.0) { miao_exec(["lineage", "-n", "15"], "lineage") }
    if miao_btn("mi_yy", x + 14.0 + bw, y, bw, "YinYang", "diff", 0.0) { miao_exec(["yinyang"], "yinyang") }
    bind y = y + 26.0
    if miao_btn("mi_pil", x + 10.0, y, bw, "Pilgrimage", "pull", 0.0) { miao_exec(["pilgrimage"], "pilgrimage") }
    if miao_btn("mi_tri", x + 14.0 + bw, y, bw, "Tribute", "push", 0.0) { miao_exec(["tribute"], "tribute") }
    bind y = y + 26.0
    if miao_btn("mi_chron", x + 10.0, y, bw, "Chronicle", "reflog", 0.0) { miao_exec(["chronicle"], "chronicle") }
    if miao_btn("mi_fair", x + 14.0 + bw, y, bw, "Fair", "serve", 0.0) { miao_exec(["fair"], "fair") }
    bind y = y + 30.0
    # the rite (commit)
    g_text(x + 10.0, y + 1.0, 10.0, C_TEXT_DIM, "RITE (commit) - the message:")
    bind y = y + 14.0
    bind msg = u_textfield("mi_msg", x + 10.0, y, w - 86.0, 22.0, st_get("mi_msg", ""))
    st_set("mi_msg", msg)
    bind fire = u_button("mi_rite", x + w - 70.0, y, 60.0, 22.0, "Rite", C_SEL)
    if st_get("focus", "") == "mi_msg" && key_pressed("enter") && st_get("modal", "") == "" { bind fire = true }
    if fire && str_trim(msg) != "" {
        miao_exec(["rite", "-m", str_trim(msg)], "rite")
        st_set("mi_msg", "")
        text_set("")
    }
    bind y = y + 30.0
    # ceremonies that take a name / path
    g_text(x + 10.0, y + 1.0, 10.0, C_TEXT_DIM, "WITH THE ARGUMENT BELOW:")
    bind y = y + 14.0
    bind arg = u_textfield("mi_arg", x + 10.0, y, w - 20.0, 22.0, st_get("mi_arg", ""))
    st_set("mi_arg", arg)
    bind y = y + 26.0
    bind tw = floor((w - 32.0) / 3.0)
    bind a = str_trim(arg)
    if miao_btn("mi_summon", x + 10.0, y, tw, "Summon", "switch", 0.0) { if a != "" { miao_exec(["summon", a], "summon " + a) } }
    if miao_btn("mi_soul", x + 14.0 + tw, y, tw, "Soul", "branch", 0.0) { miao_exec(sel(a == "", ["soul"], ["soul", a]), "soul") }
    if miao_btn("mi_harm", x + 18.0 + tw * 2.0, y, tw, "Harmonize", "merge", 0.0) { if a != "" { miao_exec(["harmonize", a], "harmonize " + a) } }
    bind y = y + 26.0
    if miao_btn("mi_tal", x + 10.0, y, tw, "Talisman", "tag", 0.0) { miao_exec(sel(a == "", ["talisman"], ["talisman", a]), "talisman") }
    if miao_btn("mi_mani", x + 14.0 + tw, y, tw, "Manifest", "show", 0.0) { miao_exec(sel(a == "", ["manifest"], ["manifest", a]), "manifest") }
    if miao_btn("mi_rest", x + 18.0 + tw * 2.0, y, tw, "Restore", "restore", 0.0) { if a != "" { miao_exec(["restore", a], "restore " + a) } }
    bind y = y + 26.0
    if miao_btn("mi_withdraw", x + 10.0, y, tw, "Withdraw", "unstage", 0.0) { if a != "" { miao_exec(["withdraw", a], "withdraw " + a) } }
    if miao_btn("mi_mother", x + 14.0 + tw, y, tw, "Mother", "remote", 0.0) { miao_exec(sel(a == "", ["mother"], ["mother", a]), "mother") }
    if miao_btn("mi_keeperb", x + 18.0 + tw * 2.0, y, tw, "Keeper", "config", 0.0) { miao_exec(sel(a == "", ["keeper"], ["keeper", a]), "keeper") }
    bind y = y + 32.0
    # output
    if wide {
        miao_out_view(px + w + 8.0, py + 4.0, pw - w - 16.0, ph - 8.0)
    } else {
        miao_out_view(x + 4.0, y, w - 8.0, ph - (y - py) - 6.0)
    }
    return 0.0
}

fn miao_out_view(x, y, w, h) {
    if h < 40.0 { return 0.0 }
    g_rect(x, y, w, h, E_BG)
    px_outline(x, y, w, h, C_LINE_SOFT, 0.8)
    bind t = st_get("mi_out", "")
    if t == "" {
        g_text(x + 10.0, y + 8.0, 11.0, C_TEXT_DIM, "Ceremony output appears here.")
        return 0.0
    }
    bind lines = str_split(t, "\n")
    bind n = len(lines)
    bind lh = 15.0
    bind maxoff = max(0.0, n * lh - h + 10.0)
    bind off = st_get("mi_off", 0.0 - 1.0)
    if off < 0.0 || off > maxoff - lh { bind off = maxoff }
    bind noff = u_scroll_y("mi_osb", x, y, w, h, n * lh + 10.0, off)
    if noff != off { bind off = noff }
    if off >= maxoff - 1.0 { st_set("mi_off", 0.0 - 1.0) } else { st_set("mi_off", off) }
    u_clip(x + 2.0, y + 2.0, w - 4.0, h - 4.0)
    bind i = floor(off / lh)
    bind last = min(n, i + ceil(h / lh) + 1.0)
    while i < last {
        bind ln = list_get(lines, i)
        bind col = E_TEXT
        if str_starts_with(ln, "$") { bind col = E_INFO }
        else if str_find(str_lower(ln), "error") >= 0.0 || str_find(ln, "✗") >= 0.0 { bind col = E_ERROR }
        else if str_find(ln, "✦") >= 0.0 || str_find(ln, "+") == 0.0 { bind col = E_STRING }
        else if str_find(ln, "-") == 0.0 { bind col = E_FUNC }
        g_text_fit(x + 8.0, y + 6.0 + i * lh - off, w - 24.0, 11.0, col, ln)
        bind i = i + 1.0
    }
    u_unclip()
    if miao_busy() {
        bind sp = floor(st_get("now", 0.0) * 8.0) % 4.0
        g_text_r(x + w - 14.0, y + 6.0, 11.0, ds_col("ctl"), st_get("mi_label", "") + str_repeat(".", sp + 1.0))
    }
    return 0.0
}

← Back to index