منبع منتشرشده
دانلود ZIP منبع
CH-J Tools
پوشهها و فایلهای یک نسخهٔ مشخص از برنامه را مرور کنید.
CH-J / Server Manager
1704 فایل
0.1.2-beta
افزودهشده: · ساختهشده:
1
// Terminal autocomplete: reads only what the terminal displayed on the current line, so input that is not echoed2
// (passwords) never reaches it. It inserts text and never presses Enter; Rust Core picks the suggestions.3
(function (root) {4
const DEBOUNCE_MS = 120;5
const MIN_LENGTH = 2;6
// Text that looks like a masked password stays out of the lookup.7
const lookupText = (text) => (text.length < MIN_LENGTH || text.length > 200 || /^[*•·\s]+$/.test(text) ? "" : text);8
// What to send: only the missing rest when the typed text is a prefix, otherwise erase it first. Never a line break.9
function insertion(item, typed) {10
const insert = String(item.insert).replace(/[\r\n]/g, "");11
if (!item.replacesInput && insert.startsWith(typed)) return insert.slice(typed.length);12
return "\x7f".repeat(typed.length) + insert;13
}14
function classifyInput(data) {15
if (data === "\r" || data === "\n" || data === "\x03" || data === "\x04" || data === "\x15") return "line-end";16
// Answers the terminal itself sends to the server (cursor position, device attributes, focus), not typing.17
if (/^\x1b(\[[?>]?[\d;]*[Rcn]|\[[IO]|\].*|P.*)$/s.test(data)) return "report";18
if (data.startsWith("\x1b")) return "navigation";19
if (data === "\t" || data === "\x7f" || data === "\b") return "edit";20
if (/[\r\n]/.test(data)) return "line-end";21
return "text";22
}24
function attach({ terminal, host, t, confirm, sessionId, query, send, enabled = () => true, hideCommand = async () => {} }) {25
let start = null, suppressed = false, armed = null, timer = null, generation = 0, items = [], selected = 0, heading = "";26
const box = host.ownerDocument.createElement("div");27
box.className = "terminal-suggest"; box.hidden = true; box.dir = "ltr";28
host.append(box);29
const hide = () => { items = []; box.hidden = true; box.replaceChildren(); };30
const reset = () => { start = null; suppressed = false; armed = null; generation += 1; clearTimeout(timer); hide(); };31
const position = () => {32
const buffer = terminal.buffer.active;33
return { line: buffer.baseY + buffer.cursorY, col: buffer.cursorX };34
};35
// The line from where typing started up to the cursor, following wrapped rows.36
function currentText() {37
const buffer = terminal.buffer.active;38
if (!start || buffer.type !== "normal") return "";39
const end = position();40
if (end.line < start.line || end.line - start.line > 8) return "";41
let text = "";42
for (let line = start.line; line <= end.line; line += 1) {43
const row = buffer.getLine(line);44
if (!row) return "";45
text += row.translateToString(false, line === start.line ? start.col : 0, line === end.line ? end.col : terminal.cols);46
}47
return text;48
}49
function badge(text, kind = "") {50
const element = host.ownerDocument.createElement("span");51
element.className = `terminal-suggest-badge ${kind}`.trim(); element.textContent = text;52
return element;53
}54
function render() {55
box.replaceChildren();56
if (!items.length) { box.hidden = true; return; }57
items.forEach((item, index) => {58
const row = host.ownerDocument.createElement("button");59
row.type = "button"; row.tabIndex = -1; row.className = `terminal-suggest-item${index === selected ? " selected" : ""}`;60
const command = host.ownerDocument.createElement("span");61
command.className = "terminal-suggest-command"; command.textContent = item.command;62
const meta = host.ownerDocument.createElement("span");63
meta.className = "terminal-suggest-meta"; meta.append(item.title);64
if (item.level === "tested") meta.append(badge(t("terminal.suggest.tested"), "tested"));65
else if (item.level === "proposed") meta.append(badge(t("terminal.suggest.proposed")));66
else if (item.level === "discovered") meta.append(badge(t("terminal.suggest.discovered")));67
else if (item.level === "custom") meta.append(badge(t("terminal.suggest.custom")));68
else if (item.level === "corrected") meta.append(badge(t("terminal.suggest.corrected"), "high"));69
else if (item.level === "file") meta.append(badge(t("terminal.suggest.file")));70
else if (item.level === "directory") meta.append(badge(t("terminal.suggest.directory")));71
if (item.risk === "critical") meta.append(badge(t("terminal.suggest.criticalRisk"), "critical"));72
if (item.risk === "high") meta.append(badge(t("terminal.suggest.highRisk"), "high"));73
if (item.available === false) meta.append(badge(t("terminal.suggest.notInstalled")));74
if (item.privileged) meta.append(badge(t("terminal.suggest.privileged")));75
if (item.powershell) meta.append(badge(t("terminal.suggest.powershell")));76
row.append(command, meta);77
row.addEventListener("pointerdown", (event) => { event.preventDefault(); selected = index; accept(); terminal.focus(); });78
box.append(row);79
});80
const footer = host.ownerDocument.createElement("div");81
footer.className = "terminal-suggest-footer"; footer.textContent = `${heading ? `${heading} · ` : ""}${t("terminal.suggest.hint")}`;82
box.append(footer);83
box.hidden = false;84
// The text area can be shorter than its host and start below its top edge, so measure the rendered rows.85
const screen = host.querySelector?.(".xterm-screen");86
const hostRect = host.getBoundingClientRect?.(), screenRect = screen?.getBoundingClientRect?.();87
const offset = hostRect && screenRect ? screenRect.top - hostRect.top : 0;88
const cell = (screenRect?.height || host.clientHeight) / Math.max(terminal.rows, 1);89
const rowTop = offset + terminal.buffer.active.cursorY * cell;90
box.style.left = "8px";91
if (rowTop + cell + box.offsetHeight <= host.clientHeight) { box.style.top = `${rowTop + cell}px`; box.style.bottom = ""; }92
else { box.style.top = ""; box.style.bottom = `${host.clientHeight - rowTop}px`; }93
}94
function accept() {95
const id = sessionId(), item = items[selected];96
if (!id || !item) return;97
// Typing may have gone on since the list was built: work from what the line shows now.98
send(id, insertion(item, currentText().trimStart()));99
armed = item.risk === "critical" ? item.command : null;100
// A directory goes on: its content is the next list.101
suppressed = !item.continues; hide();102
}103
async function lookup() {104
const id = enabled() ? sessionId() : null, text = lookupText(suppressed ? "" : currentText()), token = ++generation;105
if (!id || !text) { hide(); return; }106
let result;107
try { result = await query(id, text); } catch { return; }108
if (token !== generation || !result?.ok) return;109
selected = 0; items = result.value.items || [];110
heading = [result.value.name, result.value.version].filter(Boolean).join(" ");111
render();112
}113
const schedule = () => { clearTimeout(timer); timer = setTimeout(lookup, DEBOUNCE_MS); };115
terminal.onWriteParsed?.(() => { if (start && !suppressed) schedule(); });116
terminal.attachCustomKeyEventHandler((event) => {117
if (event.type !== "keydown") return true;118
if (event.key === "Enter" && armed) {119
if (!confirm(t("terminal.suggest.confirmCritical", { command: armed }))) { event.preventDefault(); return false; }120
armed = null;121
return true;122
}123
if (box.hidden) return true;124
if (event.key === "ArrowDown" || event.key === "ArrowUp") {125
selected = (selected + (event.key === "ArrowDown" ? 1 : items.length - 1)) % items.length; render(); event.preventDefault(); return false;126
}127
if (event.key === "ArrowRight" && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) { accept(); event.preventDefault(); return false; }128
// Tab stays with the shell, except for a repair the shell could not make itself.129
if (event.key === "Tab" && !event.shiftKey && (items[selected]?.level === "corrected" || (items[selected]?.replacesInput && ["file", "directory"].includes(items[selected]?.level)))) { accept(); event.preventDefault(); return false; }130
if (event.key === "Escape") { suppressed = true; hide(); event.preventDefault(); return false; }131
if (event.key === "Delete" && event.shiftKey) {132
const item = items[selected];133
void hideCommand(String(item.command).replace(/^(?:(?:sudo|doas|time|nohup|command|exec)\s+(?:-\S+\s+)*)+/, "")).then(schedule);134
items = items.filter((entry) => entry !== item); selected = 0; render();135
event.preventDefault(); return false;136
}137
return true;138
});139
return {140
reset,141
onInput(data) {142
const kind = classifyInput(data);143
if (kind === "line-end") { reset(); return; }144
if (kind === "report") return;145
if (kind === "navigation") { suppressed = true; hide(); return; }146
if (kind === "text" && !start) start = position();147
}148
};149
}150
root.CHJ_TERMINAL_SUGGEST = Object.freeze({ attach, insertion, classifyInput, lookupText });151
})(globalThis);SHA-256
819f194e140a3c25670aa5fdd73d7dc54a901b2bf36d94c586c8a4863c72cdaaCH-J Proprietary Software License 1.14 · کد منبع تحت CH-J Proprietary Software License 1.14 ارائه میشود. در دسترس بودن آن شرایط مجوز را تغییر نمیدهد و حق اضافهای اعطا نمیکند.
SHA-256 بایگانی
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f