Orginize
This commit is contained in:
@@ -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 []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user