CH-J ToolsSSH کے ذریعے سرور کا انتظام
مینو
شائع شدہ سورس

CH-J Tools

ایپلیکیشن کی کسی مخصوص ریلیز کی ڈائریکٹریز اور فائلیں دیکھیں۔

سورس ZIP ڈاؤن لوڈ کریں
CH-J / Server Manager 1704 فائلیں
0.1.2-beta شامل کیا گیا: · بلڈ کیا گیا:
terminalSuggest.js شامل کیا گیا: تاریخ درج نہیں 8.9 KB · 151 لائنیں
کوڈ فائل ڈاؤن لوڈ کریں
1// Terminal autocomplete: reads only what the terminal displayed on the current line, so input that is not echoed
2// (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-256819f194e140a3c25670aa5fdd73d7dc54a901b2bf36d94c586c8a4863c72cdaa

CH-J Proprietary Software License 1.14 · سورس CH-J Proprietary Software License 1.14 کے تحت فراہم کیا جاتا ہے۔ اس کی دستیابی لائسنس کی شرائط نہیں بدلتی اور نہ ہی اضافی حقوق دیتی ہے۔

آرکائیو کا SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
فائلیں