灵符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/util.ling
# rt_data.ling - text records and small data helpers shared by the editor and exported games.
# Scene files are line-oriented: one record per line, fields separated by "|", each field "key=value".
# Values are escaped so a field can hold any text: % -> %25   | -> %7C   = -> %3D   newline -> %0A

fn esc(s) {
    bind t = str_replace(s, "%", "%25")
    bind t = str_replace(t, "|", "%7C")
    bind t = str_replace(t, "=", "%3D")
    bind t = str_replace(t, "\n", "%0A")
    return str_replace(t, "\r", "")
}
fn unesc(s) {
    bind t = str_replace(s, "%0A", "\n")
    bind t = str_replace(t, "%3D", "=")
    bind t = str_replace(t, "%7C", "|")
    return str_replace(t, "%25", "%")
}

# the part of a field before / after its first "="
fn kv_key(field) {
    bind i = str_find(field, "=")
    if i < 0.0 { return str_trim(field) }
    return str_trim(str_slice(field, 0.0, i))
}
fn kv_val(field) {
    bind i = str_find(field, "=")
    if i < 0.0 { return "" }
    return unesc(str_slice(field, i + 1.0, str_len(field) - i - 1.0))
}
# value of `key` in a record (list of fields), or `d` when absent
fn rec_get(fields, key, d) {
    bind i = 0.0
    while i < len(fields) {
        bind f = list_get(fields, i)
        if kv_key(f) == key { return kv_val(f) }
        bind i = i + 1.0
    }
    return d
}
fn rec_num(fields, key, d) {
    bind v = rec_get(fields, key, "")
    if v == "" { return d }
    return to_number(v)
}
fn rec_has(fields, key) {
    bind i = 0.0
    while i < len(fields) {
        if kv_key(list_get(fields, i)) == key { return true }
        bind i = i + 1.0
    }
    return false
}
# "k=v" field builders
fn fld(k, v) { return "|" + k + "=" + esc("" + v) }
fn fldn(k, v) { return "|" + k + "=" + num_str(round(v * 10000.0) / 10000.0) }

# "a,b,c" <-> numbers
fn nums_parse(s) {
    bind out = []
    bind p = str_split(s, ",")
    bind i = 0.0
    while i < len(p) {
        bind t = str_trim(list_get(p, i))
        if t != "" { bind out = list_push(out, to_number(t)) }
        bind i = i + 1.0
    }
    return out
}
fn nums_text(l) {
    bind s = ""
    bind i = 0.0
    while i < len(l) {
        if i > 0.0 { bind s = s + "," }
        bind s = s + num_str(round(list_get(l, i) * 10000.0) / 10000.0)
        bind i = i + 1.0
    }
    return s
}
fn nums_get(l, i, d) {
    if i < len(l) { return list_get(l, i) }
    return d
}

# path helpers (forward slashes everywhere)
fn p_norm(p) { return str_replace(p, "\\", "/") }
fn p_base(p) {
    bind q = p_norm(p)
    bind parts = str_split(q, "/")
    bind i = len(parts) - 1.0
    while i >= 0.0 {
        if list_get(parts, i) != "" { return list_get(parts, i) }
        bind i = i - 1.0
    }
    return ""
}
fn p_dir(p) {
    bind q = p_norm(p)
    bind i = str_len(q) - 1.0
    while i >= 0.0 {
        if str_slice(q, i, 1.0) == "/" { return str_slice(q, 0.0, i) }
        bind i = i - 1.0
    }
    return ""
}
fn p_ext(p) {
    bind b = p_base(p)
    bind parts = str_split(b, ".")
    if len(parts) < 2.0 { return "" }
    return str_lower(list_get(parts, len(parts) - 1.0))
}
fn p_stem(p) {
    bind b = p_base(p)
    bind e = p_ext(p)
    if e == "" { return b }
    return str_slice(b, 0.0, str_len(b) - str_len(e) - 1.0)
}
fn p_join(a, b) {
    if a == "" { return b }
    if b == "" { return a }
    bind q = p_norm(a)
    if str_ends_with(q, "/") { return q + b }
    return q + "/" + p_norm(b)
}
fn p_is_abs(p) {
    bind q = p_norm(p)
    if str_starts_with(q, "/") { return true }
    if str_len(q) > 1.0 && str_slice(q, 1.0, 1.0) == ":" { return true }
    # LingOS paths carry no leading slash: its roots are absolute as they are
    # (otherwise the working directory gets joined in front and paths double up).
    if str_starts_with(q, "home/") || str_starts_with(q, "media/") || str_starts_with(q, "apps/") || str_starts_with(q, "projects/") || str_starts_with(q, "packages/") || str_starts_with(q, "pkg-") { return true }
    return false
}
# make `p` relative to `root` when it lies inside it
fn p_rel(root, p) {
    bind r = p_norm(root)
    bind q = p_norm(p)
    if !str_ends_with(r, "/") { bind r = r + "/" }
    if str_starts_with(str_lower(q), str_lower(r)) { return str_slice(q, str_len(r), str_len(q) - str_len(r)) }
    return q
}

# list helpers (lists are values: always rebind the result)
fn l_has(l, v) {
    bind i = 0.0
    while i < len(l) {
        if list_get(l, i) == v { return true }
        bind i = i + 1.0
    }
    return false
}
fn l_index(l, v) {
    bind i = 0.0
    while i < len(l) {
        if list_get(l, i) == v { return i }
        bind i = i + 1.0
    }
    return 0.0 - 1.0
}
fn l_remove_at(l, k) {
    bind out = []
    bind i = 0.0
    while i < len(l) {
        if i != k { bind out = list_push(out, list_get(l, i)) }
        bind i = i + 1.0
    }
    return out
}
fn l_set_at(l, k, v) {
    bind out = []
    bind i = 0.0
    while i < len(l) {
        if i == k { bind out = list_push(out, v) } else { bind out = list_push(out, list_get(l, i)) }
        bind i = i + 1.0
    }
    return out
}
fn l_insert_at(l, k, v) {
    bind out = []
    bind i = 0.0
    while i < len(l) {
        if i == k { bind out = list_push(out, v) }
        bind out = list_push(out, list_get(l, i))
        bind i = i + 1.0
    }
    if k >= len(l) { bind out = list_push(out, v) }
    return out
}
# store-list helpers: remove / insert in a st_ list
fn sl_remove(key, k) {
    st_ldel(key, k)
}
fn sl_insert(key, k, v) {
    bind n = st_llen(key)
    st_lpush(key, v)
    bind i = n
    while i > k {
        st_lset(key, i, st_lget(key, i - 1.0))
        bind i = i - 1.0
    }
    st_lset(key, k, v)
}
fn sl_to_list(key) {
    bind out = []
    bind i = 0.0
    while i < st_llen(key) {
        bind out = list_push(out, st_lget(key, i))
        bind i = i + 1.0
    }
    return out
}
fn sl_from_list(key, l) {
    st_lclear(key)
    bind i = 0.0
    while i < len(l) {
        st_lpush(key, list_get(l, i))
        bind i = i + 1.0
    }
}

# simple key=value settings text
fn cfg_get(text, key, d) {
    bind lines = str_split(text, "\n")
    bind i = 0.0
    while i < len(lines) {
        bind ln = str_trim(list_get(lines, i))
        if str_starts_with(ln, key + "=") { return str_slice(ln, str_len(key) + 1.0, str_len(ln) - str_len(key) - 1.0) }
        bind i = i + 1.0
    }
    return d
}
fn cfg_all(text, key) {
    bind out = []
    bind lines = str_split(text, "\n")
    bind i = 0.0
    while i < len(lines) {
        bind ln = str_trim(list_get(lines, i))
        if str_starts_with(ln, key + "=") { bind out = list_push(out, str_slice(ln, str_len(key) + 1.0, str_len(ln) - str_len(key) - 1.0)) }
        bind i = i + 1.0
    }
    return out
}
# strip surrounding quotes
fn unquote(s) {
    bind t = str_trim(s)
    if str_len(t) >= 2.0 && str_starts_with(t, "\"") && str_ends_with(t, "\"") { return str_slice(t, 1.0, str_len(t) - 2.0) }
    return t
}

# truthiness of a flag that may arrive as a number (1 / 0) or a bool (fs_list's "is directory")
fn is_true(v) {
    if v == true { return true }
    if v == 1.0 { return true }
    return false
}

# is node `id` in the editor's selection (an empty list in games)
fn rt_selected(id) {
    bind n = st_llen("sel_ids")
    bind i = 0.0
    while i < n {
        if st_lget("sel_ids", i) == id { return true }
        bind i = i + 1.0
    }
    return false
}

← Back to index