This commit is contained in:
2026-08-18 18:11:28 -04:00
parent 0e9a650fa2
commit bbaf47028f
259 changed files with 0 additions and 0 deletions
@@ -0,0 +1,286 @@
/** Syntax highlighting for /bin/edit (line-oriented, best-effort). */
const EDIT_KW_JS =
/^(?:const|let|var|function|return|async|await|if|else|for|while|do|switch|case|break|continue|default|try|catch|finally|throw|new|typeof|instanceof|in|of|class|extends|super|this|static|import|export|from|as|default|void|delete|yield|enum|interface|type|public|private|protected|readonly)$/
/**
* @param {string} path
*/
function bareEditDetectLang(path) {
const p = String(path || '').toLowerCase()
const dot = p.lastIndexOf('.')
const ext = dot >= 0 ? p.slice(dot) : ''
if (ext === '.json') return 'json'
if (ext === '.md' || ext === '.markdown') return 'md'
if (ext === '.sh' || ext === '.bash' || ext === '.zsh') return 'shell'
if (
ext === '.js' ||
ext === '.mjs' ||
ext === '.cjs' ||
ext === '.ts' ||
ext === '.tsx' ||
ext === '.jsx'
)
return 'js'
return 'plain'
}
/**
* Merge overlapping spans (later wins) — not used if we build non-overlapping.
* @param {Array<{ start: number, end: number, cls: string }>} spans
*/
function bareEditMergeSpans(spans) {
const s = spans.filter((x) => x.end > x.start).sort((a, b) => a.start - b.start || b.end - a.end)
/** @type {typeof spans} */
const out = []
for (const cur of s) {
const last = out[out.length - 1]
if (!last || cur.start >= last.end) {
out.push({ ...cur })
} else if (cur.end > last.end) {
if (cur.start > last.start) {
out[out.length - 1] = { start: last.start, end: cur.start, cls: last.cls }
out.push({ ...cur })
} else {
out[out.length - 1] = { ...cur }
}
}
}
return out
}
/**
* @param {string} line
* @returns {Array<{ start: number, end: number, cls: string }>}
*/
function bareEditSpansStringsCommentsJs(line) {
/** @type {Array<{ start: number, end: number, cls: string }>} */
const spans = []
let i = 0
while (i < line.length) {
const c = line[i]
const next = line[i + 1]
if (c === '/' && next === '/') {
spans.push({ start: i, end: line.length, cls: 'comment' })
break
}
if (c === '/' && next === '*') {
let j = i + 2
while (j < line.length - 1) {
if (line[j] === '*' && line[j + 1] === '/') {
j += 2
break
}
j++
}
if (j > line.length) j = line.length
spans.push({ start: i, end: j, cls: 'comment' })
i = j
continue
}
if (c === '"' || c === "'" || c === '`') {
const q = c
const start = i
i++
while (i < line.length) {
if (line[i] === '\\') {
i += 2
continue
}
if (line[i] === q) {
i++
break
}
i++
}
spans.push({ start, end: i, cls: 'string' })
continue
}
i++
}
return bareEditMergeSpans(spans)
}
/**
* @param {string} segment code only (no strings/comments inside)
*/
function bareEditSpansKeywordsNumbers(segment, offset) {
/** @type {Array<{ start: number, end: number, cls: string }>} */
const out = []
const re = /\b([A-Za-z_$][\w$]*)\b|\b(\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b/g
let m
while ((m = re.exec(segment)) !== null) {
if (m[1]) {
if (EDIT_KW_JS.test(m[1])) {
out.push({
start: offset + m.index,
end: offset + m.index + m[1].length,
cls: 'keyword'
})
}
} else if (m[2]) {
out.push({
start: offset + m.index,
end: offset + m.index + m[2].length,
cls: 'number'
})
}
}
return out
}
/**
* @param {string} line
* @param {number} gapStart
* @param {number} gapEnd
* @param {Array<{ start: number, end: number, cls: string }>} base
*/
function bareEditFillGapKeywords(line, gapStart, gapEnd, base) {
if (gapEnd <= gapStart) return
const seg = line.slice(gapStart, gapEnd)
const extra = bareEditSpansKeywordsNumbers(seg, gapStart)
for (const e of extra) base.push(e)
}
/**
* @param {string} line
*/
function bareEditHighlightJsLine(line) {
const sc = bareEditSpansStringsCommentsJs(line)
if (sc.length === 0) {
const all = /** @type {typeof sc} */ ([])
bareEditFillGapKeywords(line, 0, line.length, all)
return all.sort((a, b) => a.start - b.start)
}
/** @type {typeof sc} */
const out = [...sc]
let cursor = 0
for (const sp of sc) {
bareEditFillGapKeywords(line, cursor, sp.start, out)
cursor = sp.end
}
bareEditFillGapKeywords(line, cursor, line.length, out)
return out.sort((a, b) => a.start - b.start)
}
/**
* @param {string} line
*/
function bareEditHighlightJsonLine(line) {
const t = line.trimStart()
if (t.startsWith('//')) {
return [{ start: line.indexOf('//'), end: line.length, cls: 'comment' }]
}
/** @type {Array<{ start: number, end: number, cls: string }>} */
const spans = []
const re = /("(?:\\.|[^"\\])*")\s*:|("(?:\\.|[^"\\])*")|(\btrue|false|null\b)|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g
let m
while ((m = re.exec(line)) !== null) {
if (m[1]) {
const keyEnd = m.index + m[1].length
spans.push({ start: m.index, end: keyEnd, cls: 'keyword' })
} else if (m[2]) {
spans.push({
start: m.index,
end: m.index + m[2].length,
cls: 'string'
})
} else if (m[3]) {
spans.push({
start: m.index,
end: m.index + m[3].length,
cls: 'keyword'
})
} else if (m[4]) {
spans.push({
start: m.index,
end: m.index + m[4].length,
cls: 'number'
})
}
}
return spans.sort((a, b) => a.start - b.start)
}
/**
* @param {string} line
*/
function bareEditHighlightShellLine(line) {
const idx = line.indexOf('#')
if (idx >= 0) {
return [{ start: idx, end: line.length, cls: 'comment' }]
}
/** @type {Array<{ start: number, end: number, cls: string }>} */
const spans = []
const kw =
/^\s*(if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|readonly|source|\.)[\s#;]|\b(if|then|else|elif|fi|for|in|do|done|case|esac|function|return|export|local|readonly)\b/g
let m
while ((m = kw.exec(line)) !== null) {
const word = m[1] || m[2]
if (!word) continue
const start = m.index + m[0].indexOf(word)
spans.push({ start, end: start + word.length, cls: 'keyword' })
}
const dq = /"(?:\\.|[^"\\])*"/g
while ((m = dq.exec(line)) !== null) {
spans.push({ start: m.index, end: m.index + m[0].length, cls: 'string' })
}
const sq = /'[^']*'/g
while ((m = sq.exec(line)) !== null) {
spans.push({ start: m.index, end: m.index + m[0].length, cls: 'string' })
}
return spans.sort((a, b) => a.start - b.start)
}
/**
* @param {string} line
*/
function bareEditHighlightMdLine(line) {
/** @type {Array<{ start: number, end: number, cls: string }>} */
const spans = []
if (/^\s*#{1,6}\s/.test(line)) {
const m = line.match(/^\s*(#{1,6}\s.*)$/)
if (m) {
const i = line.indexOf(m[1])
spans.push({ start: i, end: line.length, cls: 'keyword' })
}
return spans
}
if (/^\s*(?:[-*+]|\d+\.)\s/.test(line)) {
const m = line.match(/^\s*((?:[-*+]|\d+\.)\s.*)$/)
if (m) {
const i = line.indexOf(m[1])
spans.push({ start: i, end: line.length, cls: 'comment' })
}
return spans
}
const bold = /\*\*[^*]+\*\*|__[^_]+__/g
let m
while ((m = bold.exec(line)) !== null) {
spans.push({ start: m.index, end: m.index + m[0].length, cls: 'string' })
}
const code = /`[^`]+`/g
while ((m = code.exec(line)) !== null) {
spans.push({ start: m.index, end: m.index + m[0].length, cls: 'number' })
}
return spans.sort((a, b) => a.start - b.start)
}
/**
* @param {string} line
* @param {string} lang
*/
function bareEditHighlightLine(line, lang) {
switch (lang) {
case 'js':
return bareEditHighlightJsLine(line)
case 'json':
return bareEditHighlightJsonLine(line)
case 'shell':
return bareEditHighlightShellLine(line)
case 'md':
return bareEditHighlightMdLine(line)
default:
return []
}
}