micro-models-agent 2.11.0 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/assets/js/tabs.js +22 -3
- package/dist/cli/cache-line.js +1 -0
- package/dist/cli/command-suggest.js +5 -4
- package/dist/cli/commands.js +231 -122
- package/dist/cli/completer.js +132 -31
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-payload.js +4 -4
- package/dist/cli/main.js +166 -61
- package/dist/cli/onboarding-commands.js +126 -0
- package/dist/cli/plugin-commands.js +2 -1
- package/dist/cli/relaunch.js +16 -13
- package/dist/cli/repl-commands-lsp.js +75 -0
- package/dist/cli/repl-commands-skills.js +105 -0
- package/dist/cli/repl-commands.js +589 -266
- package/dist/cli/repl.js +436 -177
- package/dist/cli/run-result.js +5 -6
- package/dist/cli/security-commands.js +37 -28
- package/dist/cli/setup-order.js +4 -55
- package/dist/cli/setup-prompt.js +3 -2
- package/dist/cli/setup.js +27 -107
- package/dist/cli/web-command.js +98 -0
- package/dist/config/budget.js +5 -5
- package/dist/config/config.js +103 -96
- package/dist/config/defaults.js +38 -8
- package/dist/config/domains.js +24 -27
- package/dist/config/env.js +46 -0
- package/dist/config/experts.js +2 -0
- package/dist/config/index.js +1 -0
- package/dist/config/paths.js +56 -0
- package/dist/config/redact.js +44 -0
- package/dist/config/registry-extra.js +334 -0
- package/dist/config/registry.js +584 -0
- package/dist/config/security.js +35 -36
- package/dist/config/timeouts.js +74 -0
- package/dist/config/utils.js +16 -15
- package/dist/core/agent/artifact-progress.js +59 -0
- package/dist/core/agent/artifact-resolve.js +79 -0
- package/dist/core/agent/audit-gate.js +7 -3
- package/dist/core/agent/compaction.js +2 -2
- package/dist/core/agent/constants.js +33 -4
- package/dist/core/agent/context-log.js +454 -0
- package/dist/core/agent/context-renderer.js +5 -0
- package/dist/core/agent/critic-gate.js +132 -0
- package/dist/core/agent/hallucination-gate.js +148 -38
- package/dist/core/agent/loop-state.js +15 -0
- package/dist/core/agent/prefix-monitor.js +1 -1
- package/dist/core/agent/reasoning-resolver.js +10 -1
- package/dist/core/agent/token-tracker.js +7 -4
- package/dist/core/agent/tool-batch.js +93 -18
- package/dist/core/agent/tool-images.js +18 -0
- package/dist/core/agent/tool-output.js +65 -11
- package/dist/core/agent/tool-pairing.js +60 -0
- package/dist/core/agent-moe.js +25 -24
- package/dist/core/agent.js +429 -108
- package/dist/core/bootstrap/agent-phase.js +66 -0
- package/dist/core/bootstrap/config-phase.js +86 -0
- package/dist/core/bootstrap/modules-phase.js +269 -0
- package/dist/core/bootstrap/provider-phase.js +183 -0
- package/dist/core/bootstrap/session-phase.js +204 -0
- package/dist/core/bootstrap/state.js +94 -0
- package/dist/core/bootstrap/workspace-phase.js +115 -0
- package/dist/core/bootstrap.js +92 -660
- package/dist/core/crash-handler.js +17 -7
- package/dist/core/environment.js +37 -12
- package/dist/core/host-plan.js +43 -0
- package/dist/core/host.js +23 -0
- package/dist/core/index.js +1 -0
- package/dist/core/prompt-builder.js +17 -1
- package/dist/core/prompt-overflow.js +42 -43
- package/dist/core/session-logger.js +122 -30
- package/dist/core/system-prompt.js +117 -0
- package/dist/core/version.js +6 -4
- package/dist/core/web-host.js +34 -0
- package/dist/core/workspace.js +19 -15
- package/dist/i18n/index.js +37 -3
- package/dist/index.js +7 -3
- package/dist/llm/image-utils.js +55 -64
- package/dist/llm/llm-errors.js +9 -0
- package/dist/llm/model-loader.js +10 -32
- package/dist/llm/models-dev.js +121 -0
- package/dist/llm/openai-compat.js +146 -73
- package/dist/llm/orchestrator.js +33 -11
- package/dist/llm/provider-budget.js +2 -2
- package/dist/llm/provider-context.js +233 -0
- package/dist/llm/provider.js +1 -1
- package/dist/llm/response.js +5 -0
- package/dist/llm/token-counter.js +27 -11
- package/dist/logger/app-logger.js +48 -22
- package/dist/logger/file-log.js +28 -19
- package/dist/logger/index.js +1 -0
- package/dist/main.js +273 -47
- package/dist/migration/backup.js +14 -3
- package/dist/migration/detect.js +12 -3
- package/dist/migration/index.js +1 -0
- package/dist/modules/artifacts/store.js +9 -8
- package/dist/modules/browser/actions.js +6 -0
- package/dist/modules/browser/bridge-client.js +14 -12
- package/dist/modules/browser/bridge-path.js +4 -4
- package/dist/modules/browser/console-buffer.js +37 -0
- package/dist/modules/browser/cookie-store.js +5 -0
- package/dist/modules/browser/driver-types.js +1 -0
- package/dist/modules/browser/driver.js +19 -11
- package/dist/modules/browser/index.js +3 -1
- package/dist/modules/browser/module.js +2 -1
- package/dist/modules/browser/session.js +10 -30
- package/dist/modules/browser/snapshot.js +19 -13
- package/dist/modules/browser/types.js +1 -0
- package/dist/modules/certification/cli.js +28 -20
- package/dist/modules/certification/fact-checker.js +9 -1
- package/dist/modules/certification/loader.js +9 -2
- package/dist/modules/certification/manifest.js +83 -21
- package/dist/modules/certification/runner.js +23 -13
- package/dist/modules/certification/scenarios.js +20 -20
- package/dist/modules/certification/syntax-scenarios.js +1 -2
- package/dist/modules/context/chunk-query.js +11 -9
- package/dist/modules/context/fact-extractor.js +37 -39
- package/dist/modules/context/index.js +1 -0
- package/dist/modules/context/manager.js +106 -80
- package/dist/modules/critic/client.js +176 -0
- package/dist/modules/critic/evidence.js +81 -0
- package/dist/modules/critic/provider.js +45 -0
- package/dist/modules/critic/tools.js +78 -0
- package/dist/modules/critic/types.js +1 -0
- package/dist/modules/execution/audit-runners.js +28 -34
- package/dist/modules/execution/auditor.js +53 -49
- package/dist/modules/execution/execution-plugin.js +142 -102
- package/dist/modules/execution/index.js +1 -1
- package/dist/modules/execution/input-from.js +37 -13
- package/dist/modules/execution/module.js +368 -191
- package/dist/modules/execution/moe-executor.js +53 -26
- package/dist/modules/execution/plan-actions.js +320 -202
- package/dist/modules/execution/plan-checks.js +35 -0
- package/dist/modules/execution/plan-coverage.js +36 -33
- package/dist/modules/execution/plan-steps.js +11 -3
- package/dist/modules/execution/plan-store.js +83 -17
- package/dist/modules/execution/plan-tool.js +89 -35
- package/dist/modules/execution/plan-validator.js +30 -10
- package/dist/modules/execution/planner.js +22 -13
- package/dist/modules/execution/stuck-detector.js +196 -93
- package/dist/modules/execution/tracker.js +22 -8
- package/dist/modules/execution/verifier.js +69 -48
- package/dist/modules/execution/windows-commands.js +12 -12
- package/dist/modules/hallucination/artifact-claim.js +93 -0
- package/dist/modules/hallucination/confidence.js +13 -11
- package/dist/modules/hallucination/consistency.js +10 -2
- package/dist/modules/hallucination/detector.js +10 -5
- package/dist/modules/hallucination/factual.js +43 -26
- package/dist/modules/hallucination/index.js +1 -0
- package/dist/modules/hallucination/js-identifiers.js +44 -45
- package/dist/modules/hallucination/llm-judge.js +17 -12
- package/dist/modules/hallucination/success-claim.js +59 -0
- package/dist/modules/indexer/cache.js +52 -4
- package/dist/modules/indexer/index.js +1 -0
- package/dist/modules/indexer/map-command.js +2 -4
- package/dist/modules/indexer/map-select.js +16 -16
- package/dist/modules/indexer/module.js +47 -12
- package/dist/modules/indexer/project-profile.js +8 -7
- package/dist/modules/indexer/symbols.js +15 -15
- package/dist/modules/indexer/walker.js +159 -29
- package/dist/modules/lsp/check-tool.js +7 -5
- package/dist/modules/lsp/client.js +39 -39
- package/dist/modules/lsp/command.js +17 -24
- package/dist/modules/lsp/config.js +12 -11
- package/dist/modules/lsp/index.js +1 -0
- package/dist/modules/lsp/module.js +48 -46
- package/dist/modules/lsp/probe.js +13 -14
- package/dist/modules/lsp/project-root.js +14 -17
- package/dist/modules/lsp/startup-check.js +18 -16
- package/dist/modules/mcp/client.js +59 -62
- package/dist/modules/mcp/index.js +1 -0
- package/dist/modules/mcp/module.js +66 -37
- package/dist/modules/mcp/registry.js +5 -0
- package/dist/modules/memory/index.js +1 -0
- package/dist/modules/memory/module.js +12 -8
- package/dist/modules/memory/search.js +4 -1
- package/dist/modules/memory/store.js +40 -2
- package/dist/modules/onboarding/init.js +47 -0
- package/dist/modules/onboarding/meet.js +30 -0
- package/dist/modules/onboarding/mode.js +30 -0
- package/dist/modules/onboarding/prompts.js +43 -0
- package/dist/modules/pipelines/engine.js +10 -0
- package/dist/modules/pipelines/index.js +1 -0
- package/dist/modules/pipelines/parser.js +4 -1
- package/dist/modules/pipelines/template.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
- package/dist/modules/plugins/builtin/notify.js +2 -0
- package/dist/modules/plugins/index.js +1 -0
- package/dist/modules/plugins/loader.js +18 -3
- package/dist/modules/plugins/manager.js +37 -17
- package/dist/modules/pricing/index.js +10 -11
- package/dist/modules/pricing/prices.js +12 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +27 -31
- package/dist/modules/processes/registry.js +44 -25
- package/dist/modules/processes/runner.js +4 -4
- package/dist/modules/providers/capabilities.js +28 -0
- package/dist/modules/providers/create.js +3 -4
- package/dist/modules/providers/factory.js +7 -7
- package/dist/modules/providers/fallback.js +12 -12
- package/dist/modules/providers/health.js +4 -4
- package/dist/modules/providers/manager.js +50 -19
- package/dist/modules/providers/naming.js +15 -0
- package/dist/modules/providers/preflight.js +71 -0
- package/dist/modules/providers/presets.js +39 -8
- package/dist/modules/providers/registry.js +7 -2
- package/dist/modules/reasoning/policy.js +8 -6
- package/dist/modules/reasoning/probe.js +30 -25
- package/dist/modules/security/audit-log.js +19 -36
- package/dist/modules/security/audit-notifier.js +35 -75
- package/dist/modules/security/command-validator.js +99 -51
- package/dist/modules/security/content-scanner.js +8 -8
- package/dist/modules/security/data-sanitizer.js +9 -16
- package/dist/modules/security/encryption.js +32 -52
- package/dist/modules/security/index.js +2 -1
- package/dist/modules/security/network-validator.js +120 -43
- package/dist/modules/security/path-validator.js +61 -46
- package/dist/modules/security/protected-paths.js +73 -0
- package/dist/modules/security/rate-limiter.js +17 -49
- package/dist/modules/security/security-policies.js +25 -57
- package/dist/modules/security/session-encryption.js +32 -71
- package/dist/modules/security/session-isolation.js +15 -28
- package/dist/modules/session/auto-name.js +63 -0
- package/dist/modules/session/export.js +98 -0
- package/dist/modules/session/index.js +1 -0
- package/dist/modules/session/manager.js +79 -28
- package/dist/modules/session/module.js +4 -3
- package/dist/modules/session/store.js +57 -20
- package/dist/modules/setup/answers.js +35 -0
- package/dist/modules/setup/apply.js +79 -0
- package/dist/modules/setup/discovery.js +72 -0
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/loader.js +9 -1
- package/dist/modules/skills/module.js +11 -0
- package/dist/modules/updater/changelog-reader.js +15 -16
- package/dist/modules/updater/checker.js +9 -4
- package/dist/modules/updater/dev-detect.js +7 -6
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +17 -15
- package/dist/modules/user-profile/compressor.js +2 -0
- package/dist/modules/user-profile/index.js +1 -0
- package/dist/modules/user-profile/profile.js +10 -1
- package/dist/modules/webui/answer-api.js +29 -0
- package/dist/modules/webui/assets.js +50 -0
- package/dist/modules/webui/body.js +21 -0
- package/dist/modules/webui/commands-settings.js +209 -0
- package/dist/modules/webui/commands-shared.js +60 -0
- package/dist/modules/webui/commands.js +174 -0
- package/dist/modules/webui/config-api.js +134 -0
- package/dist/modules/webui/file-api.js +202 -0
- package/dist/modules/webui/forward.js +159 -0
- package/dist/modules/webui/history-api.js +112 -0
- package/dist/modules/webui/host-state.js +60 -0
- package/dist/modules/webui/i18n-api.js +9 -0
- package/dist/modules/webui/limits.js +6 -0
- package/dist/modules/webui/log-forward.js +20 -0
- package/dist/modules/webui/memory-commands.js +64 -0
- package/dist/modules/webui/model-cache.js +18 -0
- package/dist/modules/webui/module.js +148 -0
- package/dist/modules/webui/persist.js +24 -0
- package/dist/modules/webui/prompt-io.js +97 -0
- package/dist/modules/webui/providers-api.js +337 -0
- package/dist/modules/webui/providers-view.js +56 -0
- package/dist/modules/webui/reasoning-view.js +15 -0
- package/dist/modules/webui/run-agent.js +81 -0
- package/dist/modules/webui/server.js +284 -0
- package/dist/modules/webui/sessions-api.js +178 -0
- package/dist/modules/webui/settings-api.js +561 -0
- package/dist/modules/webui/setup-api.js +201 -0
- package/dist/modules/webui/state.js +74 -0
- package/dist/modules/webui/util.js +261 -0
- package/dist/output/bus.js +7 -2
- package/dist/output/channel.js +80 -28
- package/dist/output/format.js +2 -1
- package/dist/output/index.js +1 -0
- package/dist/output/json-sink.js +5 -3
- package/dist/output/machine.js +4 -3
- package/dist/output/session-sink.js +4 -4
- package/dist/tools/approve.js +4 -0
- package/dist/tools/attach-image.js +5 -4
- package/dist/tools/auto-fixer.js +26 -44
- package/dist/tools/auto-tags.js +83 -0
- package/dist/tools/bash.js +193 -116
- package/dist/tools/browser.js +76 -31
- package/dist/tools/chunk-query.js +14 -6
- package/dist/tools/create-dir.js +9 -5
- package/dist/tools/delete-file.js +9 -5
- package/dist/tools/download-file.js +32 -7
- package/dist/tools/edit-file.js +52 -14
- package/dist/tools/enable-tools.js +6 -6
- package/dist/tools/executor.js +132 -27
- package/dist/tools/file-info.js +3 -1
- package/dist/tools/filter-tools.js +3 -3
- package/dist/tools/glob-tool.js +4 -1
- package/dist/tools/grep-tool.js +13 -10
- package/dist/tools/hidden-tools-block.js +6 -6
- package/dist/tools/index.js +16 -8
- package/dist/tools/list-dir.js +4 -2
- package/dist/tools/load-skill.js +7 -0
- package/dist/tools/mcp-call.js +15 -4
- package/dist/tools/meet-done.js +14 -0
- package/dist/tools/move-file.js +14 -7
- package/dist/tools/path-utils.js +29 -29
- package/dist/tools/pipeline-run.js +29 -7
- package/dist/tools/preview.js +1 -1
- package/dist/tools/process-ids.js +31 -0
- package/dist/tools/process-kill.js +9 -16
- package/dist/tools/process-list.js +1 -0
- package/dist/tools/process-log.js +11 -16
- package/dist/tools/process-wait.js +63 -0
- package/dist/tools/prompt-io.js +4 -3
- package/dist/tools/question.js +172 -25
- package/dist/tools/read-file.js +55 -34
- package/dist/tools/recall.js +14 -8
- package/dist/tools/registry.js +6 -5
- package/dist/tools/remember.js +8 -4
- package/dist/tools/scope-request.js +11 -6
- package/dist/tools/search-history.js +5 -2
- package/dist/tools/session-info.js +2 -0
- package/dist/tools/set-thinking.js +8 -4
- package/dist/tools/subagent.js +59 -39
- package/dist/tools/syntax-validator.js +292 -90
- package/dist/tools/user-input.js +22 -18
- package/dist/tools/web-browse.js +9 -3
- package/dist/tools/web-fetch.js +12 -23
- package/dist/tools/web-search.js +7 -5
- package/dist/tools/write-file.js +45 -15
- package/dist/ui/action-log.js +13 -0
- package/dist/ui/box.js +4 -3
- package/dist/ui/colors.js +2 -0
- package/dist/ui/diff.js +6 -5
- package/dist/ui/index.js +1 -0
- package/dist/ui/line-editor.js +230 -122
- package/dist/ui/line-math.js +10 -2
- package/dist/ui/md-formatter.js +57 -37
- package/dist/ui/output.js +8 -3
- package/dist/ui/plan-view.js +21 -10
- package/dist/ui/renderer.js +393 -67
- package/dist/ui/spinner.js +16 -5
- package/dist/ui/table.js +15 -17
- package/dist/ui/theme.js +143 -0
- package/dist/utils/child-run.js +20 -0
- package/dist/utils/error.js +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/retry.js +2 -2
- package/dist/utils/truncate.js +1 -1
- package/package.json +1 -1
package/dist/ui/line-editor.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
// Строчный редактор: raw-ввод, история, Tab-комплит, DSR-якорь кадра (см. DSR ниже).
|
|
1
2
|
import * as readline from "readline";
|
|
2
3
|
import stringWidth from "string-width";
|
|
4
|
+
import { pc } from "./colors";
|
|
5
|
+
import { readEnv } from "../config/env";
|
|
3
6
|
import { buildLayout, layoutIndexFor, visualColAt, charOffsetFor, charLen, stripAnsi, } from "./line-math";
|
|
7
|
+
/** Многострочный редактор ввода с историей и Tab-комплитом (raw-режим TTY). */
|
|
4
8
|
export class LineEditor {
|
|
5
9
|
input;
|
|
6
10
|
output;
|
|
7
|
-
/**
|
|
11
|
+
/** Хук хоста: true — поглотить клавишу до обработки редактором. */
|
|
8
12
|
onKeyInput;
|
|
9
13
|
onInterrupt;
|
|
10
14
|
promptStr;
|
|
@@ -14,55 +18,59 @@ export class LineEditor {
|
|
|
14
18
|
lines = [""];
|
|
15
19
|
row = 0;
|
|
16
20
|
col = 0;
|
|
21
|
+
/** Перенос не-TTY чанкованного ввода (частичная строка между чтениями). */
|
|
22
|
+
pendingLine = "";
|
|
17
23
|
historyIndex = -1;
|
|
18
24
|
stash = null;
|
|
19
25
|
isDone = false;
|
|
20
26
|
isPaste = false;
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
27
|
+
/** Вставленное копятся между `paste-start` и `paste-end`.
|
|
28
|
+
* Вставляется одним батчем на `paste-end`: один рендер вместо рендера на
|
|
29
|
+
* клавишу, чтобы вставка выглядела вставкой, а не быстрой печатью. */
|
|
24
30
|
pasteBuffer = "";
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
31
|
+
/** Была ли последняя вставленная клавиша голым CR: схлопывает CRLF-пару
|
|
32
|
+
* в один перевод строки (текст из буфера Windows). */
|
|
27
33
|
pasteLastWasCR = false;
|
|
28
34
|
lastWasCR = false;
|
|
29
35
|
questionCb = null;
|
|
36
|
+
/** Активная Tab-сессия комплита. Пока стоит, ↑/↓/Tab крутят кандидатов, а
|
|
37
|
+
* Esc возвращает `original`; любая другая клавиша сессию роняет. */
|
|
38
|
+
completionSession = null;
|
|
30
39
|
rawMode = false;
|
|
31
40
|
listeners = {};
|
|
32
|
-
/** stdin
|
|
33
|
-
*
|
|
41
|
+
/** stdin-хендлеры из конструктора: снимаются в close(), чтобы пересоздание
|
|
42
|
+
* редактора не копило протухшие слушатели. */
|
|
34
43
|
dsrDataHandler;
|
|
35
44
|
keypressHandler;
|
|
36
45
|
dataHandler;
|
|
37
46
|
endHandler;
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
47
|
+
/** Визуальный ряд (внутри рисуемого кадра), куда последний рендер поставил
|
|
48
|
+
* курсор. Фолбэк для возврата на первую строку кадра перед перерисовкой,
|
|
49
|
+
* пока нет абсолютного якоря. Относительные ходы ломаются при скролле
|
|
50
|
+
* терминала (кадр уезжает вверх, счётчик — нет), что дублировало
|
|
51
|
+
* многострочный ввод. */
|
|
43
52
|
prevCursorRow = 0;
|
|
44
|
-
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* never arrives (timeout / broken terminal). */
|
|
53
|
+
/** Абсолютный 1-based экранный ряд первой визуальной строки кадра. Выучен
|
|
54
|
+
* из одного DSR-ответа о позиции курсора (`ESC[6n`), шлётся раз на показ
|
|
55
|
+
* промпта. Дальше якорь ведётся детерминированной скролл-математикой, новых
|
|
56
|
+
* запросов нет (защита от рассинхрона ConPTY). Без ответа (таймаут/битый
|
|
57
|
+
* терминал) — фолбэк на относительные ходы. */
|
|
50
58
|
frameTopAbs = null;
|
|
51
|
-
/**
|
|
59
|
+
/** Ждём ли DSR-ответ (ровно один outstanding-запрос). */
|
|
52
60
|
dsrPending = false;
|
|
53
|
-
/**
|
|
61
|
+
/** Буфер частичных DSR-escape, разрезанных между чанками. */
|
|
54
62
|
dsrTail = "";
|
|
55
|
-
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
63
|
+
/** Визуальный ряд курсора ровно в момент отправки outstanding DSR-запроса.
|
|
64
|
+
* Ответ матчится против него, а не `prevCursorRow`: промежуточные рендеры
|
|
65
|
+
* перезаписывают тот, пока запрос ещё летит. */
|
|
58
66
|
dsrQueryRow = 0;
|
|
59
|
-
/**
|
|
60
|
-
*
|
|
67
|
+
/** Выкл — клавиши игнорятся (кроме Ctrl+C), пока агент стримит: терминалом
|
|
68
|
+
* в этой фазе владеет хост. */
|
|
61
69
|
inputEnabled = true;
|
|
62
|
-
/**
|
|
70
|
+
/** Вкл — кадр промпта не рисуется; экраном владеет агент. */
|
|
63
71
|
frameSuspended = false;
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
72
|
+
/** Сбрасывает `dsrPending`, если DSR-ответ не пришёл: фолбэк на относительные
|
|
73
|
+
* ходы вместо клина на битом терминале. */
|
|
66
74
|
dsrTimer = null;
|
|
67
75
|
constructor(opts) {
|
|
68
76
|
this.input = opts.input;
|
|
@@ -80,17 +88,17 @@ export class LineEditor {
|
|
|
80
88
|
if (this.rawMode)
|
|
81
89
|
this.input.setRawMode(true);
|
|
82
90
|
this.enableTerminalProtocols();
|
|
83
|
-
//
|
|
84
|
-
// readline
|
|
85
|
-
//
|
|
91
|
+
// Сырой слушатель DSR-ответов о позиции курсора. Живёт рядом с keypress-эмитером
|
|
92
|
+
// readline (оба получают каждый чанк); ответы могут приходить склеенными с
|
|
93
|
+
// обычными клавишами — отсюда глобальный regex.
|
|
86
94
|
this.dsrDataHandler = (buf) => this.consumeDsrReplies(buf.toString("utf-8"));
|
|
87
95
|
this.input.on("data", this.dsrDataHandler);
|
|
88
96
|
readline.emitKeypressEvents(this.input);
|
|
89
97
|
this.keypressHandler = (str, key) => this.onKey(str, key);
|
|
90
98
|
this.input.on("keypress", this.keypressHandler);
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
this.input.resume
|
|
99
|
+
// Редактор теперь владеет stdin: взводим flowing, даже если прошлый
|
|
100
|
+
// потребитель его стопил (напр. визард настройки).
|
|
101
|
+
this.input.resume();
|
|
94
102
|
}
|
|
95
103
|
else {
|
|
96
104
|
this.dataHandler = (buf) => this.onData(buf);
|
|
@@ -189,6 +197,7 @@ export class LineEditor {
|
|
|
189
197
|
this.col = 0;
|
|
190
198
|
this.historyIndex = -1;
|
|
191
199
|
this.stash = null;
|
|
200
|
+
this.completionSession = null;
|
|
192
201
|
this.invalidateAnchor();
|
|
193
202
|
if (this.input.isTTY)
|
|
194
203
|
this.render();
|
|
@@ -199,6 +208,7 @@ export class LineEditor {
|
|
|
199
208
|
this.lines = [""];
|
|
200
209
|
this.row = 0;
|
|
201
210
|
this.col = 0;
|
|
211
|
+
this.completionSession = null;
|
|
202
212
|
this.invalidateAnchor();
|
|
203
213
|
if (this.input.isTTY)
|
|
204
214
|
this.render();
|
|
@@ -208,15 +218,14 @@ export class LineEditor {
|
|
|
208
218
|
return;
|
|
209
219
|
this.isDone = true;
|
|
210
220
|
this.disableTerminalProtocols();
|
|
211
|
-
const inp = this.input;
|
|
212
221
|
if (this.dsrDataHandler)
|
|
213
|
-
|
|
222
|
+
this.input.removeListener("data", this.dsrDataHandler);
|
|
214
223
|
if (this.keypressHandler)
|
|
215
|
-
|
|
224
|
+
this.input.removeListener("keypress", this.keypressHandler);
|
|
216
225
|
if (this.dataHandler)
|
|
217
|
-
|
|
226
|
+
this.input.removeListener("data", this.dataHandler);
|
|
218
227
|
if (this.endHandler)
|
|
219
|
-
|
|
228
|
+
this.input.removeListener("end", this.endHandler);
|
|
220
229
|
if (this.rawMode && this.input.setRawMode) {
|
|
221
230
|
this.input.setRawMode(false);
|
|
222
231
|
}
|
|
@@ -226,34 +235,30 @@ export class LineEditor {
|
|
|
226
235
|
}
|
|
227
236
|
this.emitClose();
|
|
228
237
|
}
|
|
229
|
-
//
|
|
238
|
+
// ── Ввод ──
|
|
230
239
|
onData(buf) {
|
|
231
240
|
if (this.isDone)
|
|
232
241
|
return;
|
|
233
242
|
let text = buf.toString("utf-8");
|
|
234
243
|
text = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
235
244
|
const parts = text.split("\n");
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
this.lines = [""];
|
|
246
|
-
this.row = 0;
|
|
247
|
-
this.col = 0;
|
|
248
|
-
}
|
|
245
|
+
// Незавершённый хвост — в следующий чанк: немедленный эмит разрезал бы
|
|
246
|
+
// `"hello wor" + "ld\n"` на две неверные строки.
|
|
247
|
+
this.pendingLine += parts[0];
|
|
248
|
+
for (let i = 1; i < parts.length; i++) {
|
|
249
|
+
this.emitLine(this.pendingLine);
|
|
250
|
+
this.lines = [""];
|
|
251
|
+
this.row = 0;
|
|
252
|
+
this.col = 0;
|
|
253
|
+
this.pendingLine = parts[i];
|
|
249
254
|
}
|
|
250
255
|
}
|
|
251
256
|
onKey(str, key) {
|
|
252
257
|
if (this.isDone)
|
|
253
258
|
return;
|
|
254
259
|
if (!this.inputEnabled) {
|
|
255
|
-
//
|
|
256
|
-
//
|
|
260
|
+
// На стриме уважаются только прерывающие клавиши: Ctrl+C абортит прогон,
|
|
261
|
+
// Esc форвардится, чтобы хост реализовал двойной-Esc аборт.
|
|
257
262
|
const k = key;
|
|
258
263
|
if (k?.ctrl && k.name === "c") {
|
|
259
264
|
this.ctrlC();
|
|
@@ -265,7 +270,31 @@ export class LineEditor {
|
|
|
265
270
|
}
|
|
266
271
|
return;
|
|
267
272
|
}
|
|
268
|
-
|
|
273
|
+
// Сессия комплита приоритетнее хост-хуков: Esc обязан вернуть исходный
|
|
274
|
+
// partial, а не дёрнуть хостовый Esc-хендлинг; стрелки/Tab — крутить.
|
|
275
|
+
// Любая другая клавиша роняет сессию и обрабатывается обычно (с хост-хуком).
|
|
276
|
+
if (this.completionSession) {
|
|
277
|
+
const ck = key;
|
|
278
|
+
const plain = !ck.ctrl && !ck.meta;
|
|
279
|
+
if (ck?.name === "down" && plain) {
|
|
280
|
+
this.cycleCompletion(1);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (ck?.name === "up" && plain) {
|
|
284
|
+
this.cycleCompletion(-1);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (ck?.name === "tab") {
|
|
288
|
+
this.cycleCompletion(1);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (ck?.name === "escape") {
|
|
292
|
+
this.cancelCompletion();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
this.completionSession = null;
|
|
296
|
+
}
|
|
297
|
+
if (this.onKeyInput?.(str, key))
|
|
269
298
|
return;
|
|
270
299
|
const k = key;
|
|
271
300
|
const name = k?.name ?? "undefined";
|
|
@@ -277,10 +306,9 @@ export class LineEditor {
|
|
|
277
306
|
}
|
|
278
307
|
return;
|
|
279
308
|
}
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
// "R"). Swallow the whole sequence.
|
|
309
|
+
// Отчёт о позиции курсора (`ESC[row;colR`) — никогда не ввод: на части связок
|
|
310
|
+
// Windows/ConPTY + Bun хвостовой "R" протекает в буфер и портит ответ
|
|
311
|
+
// (каждый промпт отвечал "R"). Глотаем последовательность целиком.
|
|
284
312
|
if (/^\x1b\[\d+;\d+R$/.test(seq))
|
|
285
313
|
return;
|
|
286
314
|
if (name === "paste-start" || seq === "\x1b[200~") {
|
|
@@ -344,8 +372,8 @@ export class LineEditor {
|
|
|
344
372
|
this.lastWasCR = false;
|
|
345
373
|
return;
|
|
346
374
|
}
|
|
347
|
-
//
|
|
348
|
-
//
|
|
375
|
+
// В question-режиме LF тоже сабмитит (скрипты/пайпы шлют \n без \r спереди,
|
|
376
|
+
// в отличие от настоящих терминалов).
|
|
349
377
|
if (this.questionCb) {
|
|
350
378
|
this.submit();
|
|
351
379
|
return;
|
|
@@ -450,6 +478,7 @@ export class LineEditor {
|
|
|
450
478
|
}
|
|
451
479
|
}
|
|
452
480
|
submit() {
|
|
481
|
+
this.completionSession = null;
|
|
453
482
|
if (this.questionCb) {
|
|
454
483
|
const cb = this.questionCb;
|
|
455
484
|
this.questionCb = null;
|
|
@@ -505,15 +534,16 @@ export class LineEditor {
|
|
|
505
534
|
this.lines = [""];
|
|
506
535
|
this.row = 0;
|
|
507
536
|
this.col = 0;
|
|
537
|
+
this.completionSession = null;
|
|
508
538
|
this.invalidateAnchor();
|
|
509
539
|
this.render();
|
|
510
540
|
if (this.onInterrupt) {
|
|
511
541
|
this.onInterrupt();
|
|
512
542
|
return;
|
|
513
543
|
}
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
544
|
+
// Дефолт: эмитим, только если слушают. С нулём хендлеров process.emit("SIGINT")
|
|
545
|
+
// выполняет действие ПО УМОЛЧАНИЮ (смерть процесса); у REPL слушатель всегда
|
|
546
|
+
// стоит, так что прод не страдает.
|
|
517
547
|
if (process.listenerCount("SIGINT") > 0) {
|
|
518
548
|
process.emit("SIGINT");
|
|
519
549
|
}
|
|
@@ -526,7 +556,7 @@ export class LineEditor {
|
|
|
526
556
|
this.deleteForward();
|
|
527
557
|
}
|
|
528
558
|
}
|
|
529
|
-
//
|
|
559
|
+
// ── Примитивы правки ──
|
|
530
560
|
currentChars() {
|
|
531
561
|
return Array.from(this.lines[this.row]);
|
|
532
562
|
}
|
|
@@ -534,6 +564,7 @@ export class LineEditor {
|
|
|
534
564
|
this.lines[this.row] = chars.join("");
|
|
535
565
|
}
|
|
536
566
|
insertText(text) {
|
|
567
|
+
this.completionSession = null;
|
|
537
568
|
const sanitized = text.replace(/\r\n/g, "\n").replace(/\r/g, "");
|
|
538
569
|
const chars = this.currentChars();
|
|
539
570
|
const t = Array.from(sanitized);
|
|
@@ -542,9 +573,9 @@ export class LineEditor {
|
|
|
542
573
|
this.col += t.length;
|
|
543
574
|
this.render();
|
|
544
575
|
}
|
|
545
|
-
/**
|
|
546
|
-
*
|
|
547
|
-
*
|
|
576
|
+
/** Пакетная вставка текста под курсор. Режет по переводам строк, чтобы буфер
|
|
577
|
+
* держал строку на элемент (а не литеральные `\n`), и рендерит один раз:
|
|
578
|
+
* вставка должна выглядеть вставкой, а не печатью. */
|
|
548
579
|
insertPaste(text) {
|
|
549
580
|
const segments = text.split("\n");
|
|
550
581
|
if (segments.length === 1) {
|
|
@@ -759,6 +790,7 @@ export class LineEditor {
|
|
|
759
790
|
}
|
|
760
791
|
}
|
|
761
792
|
applyHistoryEntry() {
|
|
793
|
+
this.completionSession = null;
|
|
762
794
|
const entry = this.history[this.history.length - 1 - this.historyIndex];
|
|
763
795
|
this.lines = entry.split("\n");
|
|
764
796
|
if (this.lines.length === 0)
|
|
@@ -768,6 +800,53 @@ export class LineEditor {
|
|
|
768
800
|
this.render();
|
|
769
801
|
}
|
|
770
802
|
// ---- completion / misc ----
|
|
803
|
+
/** Replace the UTF-16 range `[from, from + removeText.length)` on the
|
|
804
|
+
* current buffer line with `insertText`, placing the cursor right after. */
|
|
805
|
+
replaceCompletionToken(from, removeText, insertText) {
|
|
806
|
+
const line = this.lines[this.row];
|
|
807
|
+
const head = line.slice(0, from);
|
|
808
|
+
const tail = line.slice(from + removeText.length);
|
|
809
|
+
this.lines[this.row] = head + insertText + tail;
|
|
810
|
+
this.col = charLen(head) + charLen(insertText);
|
|
811
|
+
}
|
|
812
|
+
/** Position of `partial` under the cursor (fallback: last in the line), or
|
|
813
|
+
* -1 when not found. */
|
|
814
|
+
partialStart(line, partial) {
|
|
815
|
+
const chars = Array.from(line);
|
|
816
|
+
const beforeCursor = chars.slice(0, this.col).join("");
|
|
817
|
+
let from = beforeCursor.lastIndexOf(partial);
|
|
818
|
+
if (from === -1)
|
|
819
|
+
from = line.lastIndexOf(partial);
|
|
820
|
+
return from;
|
|
821
|
+
}
|
|
822
|
+
applyCompletionCandidate(index) {
|
|
823
|
+
const s = this.completionSession;
|
|
824
|
+
if (!s)
|
|
825
|
+
return;
|
|
826
|
+
const chosen = s.items[index].value;
|
|
827
|
+
this.replaceCompletionToken(s.from, s.inserted, chosen);
|
|
828
|
+
s.index = index;
|
|
829
|
+
s.inserted = chosen;
|
|
830
|
+
this.render();
|
|
831
|
+
}
|
|
832
|
+
cycleCompletion(delta) {
|
|
833
|
+
const s = this.completionSession;
|
|
834
|
+
if (!s)
|
|
835
|
+
return;
|
|
836
|
+
const n = s.items.length;
|
|
837
|
+
if (n === 0)
|
|
838
|
+
return;
|
|
839
|
+
const next = (s.index + delta + n) % n;
|
|
840
|
+
this.applyCompletionCandidate(next);
|
|
841
|
+
}
|
|
842
|
+
cancelCompletion() {
|
|
843
|
+
const s = this.completionSession;
|
|
844
|
+
if (!s)
|
|
845
|
+
return;
|
|
846
|
+
this.replaceCompletionToken(s.from, s.inserted, s.original);
|
|
847
|
+
this.completionSession = null;
|
|
848
|
+
this.render();
|
|
849
|
+
}
|
|
771
850
|
tabComplete() {
|
|
772
851
|
if (!this.completer)
|
|
773
852
|
return;
|
|
@@ -778,32 +857,60 @@ export class LineEditor {
|
|
|
778
857
|
return;
|
|
779
858
|
}
|
|
780
859
|
if (matches.length === 1) {
|
|
781
|
-
//
|
|
782
|
-
//
|
|
783
|
-
|
|
784
|
-
const chars = Array.from(line);
|
|
785
|
-
const beforeCursor = chars.slice(0, this.col).join("");
|
|
786
|
-
let from = beforeCursor.lastIndexOf(partial);
|
|
787
|
-
if (from === -1)
|
|
788
|
-
from = line.lastIndexOf(partial);
|
|
860
|
+
// Дополняем partial ПОД КУРСОРОМ, а не последнее вхождение в строке: при
|
|
861
|
+
// курсоре в середине lastIndexOf() вставлял в конец строки.
|
|
862
|
+
const from = this.partialStart(line, partial);
|
|
789
863
|
if (from === -1) {
|
|
790
864
|
this.output.write("\x07");
|
|
791
865
|
return;
|
|
792
866
|
}
|
|
793
|
-
|
|
794
|
-
const tail = line.slice(from + partial.length);
|
|
795
|
-
this.lines[this.row] = head + matches[0] + tail;
|
|
796
|
-
this.col = charLen(head) + charLen(matches[0]);
|
|
867
|
+
this.replaceCompletionToken(from, partial, matches[0].value);
|
|
797
868
|
this.render();
|
|
798
869
|
return;
|
|
799
870
|
}
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
871
|
+
const from = this.partialStart(line, partial);
|
|
872
|
+
if (from === -1) {
|
|
873
|
+
this.output.write("\x07");
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
const width = this.columns();
|
|
877
|
+
const limit = width - 1;
|
|
878
|
+
const rows = matches.map((m) => {
|
|
879
|
+
const plain = m.description ? ` ${m.value} ${m.description}` : ` ${m.value}`;
|
|
880
|
+
if (limit <= 0)
|
|
881
|
+
return "";
|
|
882
|
+
if (stringWidth(plain) <= limit) {
|
|
883
|
+
return m.description ? ` ${m.value} ${pc.dim(m.description)}` : ` ${m.value}`;
|
|
884
|
+
}
|
|
885
|
+
// Режем по видимым колонкам (одних кодпоинтов мало для CJK/emoji).
|
|
886
|
+
let acc = "";
|
|
887
|
+
let w = 0;
|
|
888
|
+
for (const ch of plain) {
|
|
889
|
+
const cw = stringWidth(ch);
|
|
890
|
+
if (w + cw > limit - 1)
|
|
891
|
+
break;
|
|
892
|
+
acc += ch;
|
|
893
|
+
w += cw;
|
|
894
|
+
}
|
|
895
|
+
return `${acc}…`;
|
|
896
|
+
});
|
|
897
|
+
this.output.write(`\r\n${rows.join("\r\n")}\r\n`);
|
|
898
|
+
// Напечатанный список увёл курсор ниже старого кадра. Сбрасываем абсолютный
|
|
899
|
+
// якорь, чтобы следующий рендер позиционировался ОТНОСИТЕЛЬНО текущего
|
|
900
|
+
// курсора (под списком). Сохранение DSR-якоря возвращало кадр на старую
|
|
901
|
+
// абсолютную строку, и его `\x1b[J` стирал список: на Linux/macOS кандидаты
|
|
902
|
+
// мигали и исчезали.
|
|
903
|
+
this.invalidateAnchor();
|
|
904
|
+
// Старт сессии циклирования с первым кандидатом в буфере:
|
|
905
|
+
// ↑/↓/Tab крутят напечатанный список на месте.
|
|
906
|
+
this.completionSession = {
|
|
907
|
+
items: matches,
|
|
908
|
+
index: 0,
|
|
909
|
+
from,
|
|
910
|
+
original: partial,
|
|
911
|
+
inserted: matches[0].value,
|
|
912
|
+
};
|
|
913
|
+
this.replaceCompletionToken(from, partial, matches[0].value);
|
|
807
914
|
this.render();
|
|
808
915
|
}
|
|
809
916
|
clearScreen() {
|
|
@@ -811,7 +918,7 @@ export class LineEditor {
|
|
|
811
918
|
this.invalidateAnchor();
|
|
812
919
|
this.render();
|
|
813
920
|
}
|
|
814
|
-
//
|
|
921
|
+
// ── Рендеринг ──
|
|
815
922
|
columns() {
|
|
816
923
|
return this.output.columns || 80;
|
|
817
924
|
}
|
|
@@ -819,28 +926,27 @@ export class LineEditor {
|
|
|
819
926
|
const promptWidth = stringWidth(stripAnsi(this.promptStr));
|
|
820
927
|
return buildLayout(this.lines, this.columns(), Math.max(1, this.columns() - promptWidth));
|
|
821
928
|
}
|
|
822
|
-
/** 1-based
|
|
929
|
+
/** 1-based высота терминала, null — неизвестна (тесты / не-TTY). */
|
|
823
930
|
screenRows() {
|
|
824
931
|
const r = this.output.rows;
|
|
825
932
|
return typeof r === "number" && r > 3 ? r : null;
|
|
826
933
|
}
|
|
827
934
|
/**
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
*
|
|
831
|
-
* `MMA_DSR=1`
|
|
935
|
+
* Можно ли слать запрос позиции курсора (`ESC[6n`). На Windows — opt-in:
|
|
936
|
+
* ConPTY может декодировать ответ `ESC[row;colR` как ввод, что портит ответы
|
|
937
|
+
* (визард настройки отвечал на каждый промпт "R").
|
|
938
|
+
* `MMA_DSR=1` форсит вкл, `MMA_DSR=0` — выкл; иначе по умолчанию вкл.
|
|
832
939
|
*/
|
|
833
940
|
dsrEnabled() {
|
|
834
|
-
if (
|
|
941
|
+
if (readEnv("DSR") === "1")
|
|
835
942
|
return true;
|
|
836
|
-
if (
|
|
943
|
+
if (readEnv("DSR") === "0")
|
|
837
944
|
return false;
|
|
838
945
|
return process.platform !== "win32";
|
|
839
946
|
}
|
|
840
|
-
/**
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
* consistent regardless. */
|
|
947
|
+
/** Режет рисуемый кадр до видимого экрана. Кадр выше терминала нельзя
|
|
948
|
+
* перерисовать на месте (верх уехал в скролл), так что рисуется только хвост;
|
|
949
|
+
* абсолютный якорь держит следующий рендер консистентным в любом случае. */
|
|
844
950
|
visibleLayout() {
|
|
845
951
|
const layout = this.layout();
|
|
846
952
|
const H = this.screenRows();
|
|
@@ -850,9 +956,9 @@ export class LineEditor {
|
|
|
850
956
|
}
|
|
851
957
|
return layout;
|
|
852
958
|
}
|
|
853
|
-
/**
|
|
854
|
-
*
|
|
855
|
-
*
|
|
959
|
+
/** Забывает выученную абсолютную позицию кадра: курсор прыгнул в неизвестное
|
|
960
|
+
* место (внешний вывод, чистка экрана, свежий промпт). Следующий рендер
|
|
961
|
+
* переспросит через DSR. */
|
|
856
962
|
invalidateAnchor() {
|
|
857
963
|
this.frameTopAbs = null;
|
|
858
964
|
this.dsrPending = false;
|
|
@@ -863,10 +969,9 @@ export class LineEditor {
|
|
|
863
969
|
this.dsrTimer = null;
|
|
864
970
|
}
|
|
865
971
|
}
|
|
866
|
-
/**
|
|
867
|
-
*
|
|
868
|
-
*
|
|
869
|
-
* `screenRows()` to reject implausible ConPTY values. */
|
|
972
|
+
/** Парсит DSR-ответы `ESC[row;colR`. Буферит разрезанные escape между чанками.
|
|
973
|
+
* Принимает максимум один ответ (outstanding-запрос); лишние молча дропаются.
|
|
974
|
+
* Строку валидирует против `screenRows()`, отсекая неправдоподобные значения ConPTY. */
|
|
870
975
|
consumeDsrReplies(chunk) {
|
|
871
976
|
this.dsrTail = (this.dsrTail + chunk).slice(-80);
|
|
872
977
|
const m = /\x1b\[(\d+);(\d+)R/.exec(this.dsrTail);
|
|
@@ -881,9 +986,9 @@ export class LineEditor {
|
|
|
881
986
|
clearTimeout(this.dsrTimer);
|
|
882
987
|
this.dsrTimer = null;
|
|
883
988
|
}
|
|
884
|
-
//
|
|
885
|
-
//
|
|
886
|
-
//
|
|
989
|
+
// Верх кадра = строка ответа минус визуальный ряд курсора на момент запроса.
|
|
990
|
+
// `dsrQueryRow`, а не `prevCursorRow`: промежуточные рендеры могли двинуть
|
|
991
|
+
// курсор, пока запрос летел.
|
|
887
992
|
const top = row - this.dsrQueryRow;
|
|
888
993
|
const H = this.screenRows();
|
|
889
994
|
if (top >= 1 && (H === null || top <= H))
|
|
@@ -899,10 +1004,9 @@ export class LineEditor {
|
|
|
899
1004
|
const rowsAfter = layout.length - idx - 1;
|
|
900
1005
|
const ccol = (idx === 0 ? promptWidth : 0) + vcol;
|
|
901
1006
|
const out = [];
|
|
902
|
-
//
|
|
903
|
-
//
|
|
904
|
-
//
|
|
905
|
-
// initial reply arrives.
|
|
1007
|
+
// Возврат на первую строку кадра перед перерисовкой. Абсолютное
|
|
1008
|
+
// позиционирование (выученное одним DSR-запросом на промпт) иммунно к
|
|
1009
|
+
// скроллу; относительные ходы вверх — фолбэк до прихода первого ответа.
|
|
906
1010
|
if (this.frameTopAbs !== null) {
|
|
907
1011
|
out.push(`\x1b[${this.frameTopAbs};1H`);
|
|
908
1012
|
}
|
|
@@ -929,10 +1033,9 @@ export class LineEditor {
|
|
|
929
1033
|
out.push(`\x1b[${ccol}C`);
|
|
930
1034
|
this.output.write(out.join(""));
|
|
931
1035
|
this.prevCursorRow = idx;
|
|
932
|
-
//
|
|
933
|
-
//
|
|
934
|
-
//
|
|
935
|
-
// computed locally — no further DSR queries needed.
|
|
1036
|
+
// Детерминированная скролл-математика: обновляем якорь для СЛЕДУЮЩЕГО рендера.
|
|
1037
|
+
// После записи L строк от frameTopAbs терминал скроллится, если кадр вылез за
|
|
1038
|
+
// низ экрана; новый верх кадра считается локально, новых DSR-запросов не надо.
|
|
936
1039
|
if (this.frameTopAbs !== null) {
|
|
937
1040
|
const H = this.screenRows();
|
|
938
1041
|
if (H) {
|
|
@@ -944,9 +1047,9 @@ export class LineEditor {
|
|
|
944
1047
|
}
|
|
945
1048
|
}
|
|
946
1049
|
else if (!this.dsrPending && this.questionCb === null && this.dsrEnabled()) {
|
|
947
|
-
//
|
|
948
|
-
//
|
|
949
|
-
// dsrPending
|
|
1050
|
+
// Один DSR-запрос на кадр, дальше якорь ведёт локальная скролл-математика.
|
|
1051
|
+
// В question-режиме не спрашиваем (см. dsrEnabled). Таймаут сбрасывает
|
|
1052
|
+
// dsrPending, чтобы неотвечающий терминал откатился на относительные ходы.
|
|
950
1053
|
this.dsrPending = true;
|
|
951
1054
|
this.dsrQueryRow = idx;
|
|
952
1055
|
this.output.write("\x1b[6n");
|
|
@@ -970,6 +1073,11 @@ export class LineEditor {
|
|
|
970
1073
|
this.listeners.line?.(line);
|
|
971
1074
|
}
|
|
972
1075
|
emitClose() {
|
|
1076
|
+
// Сброс финальной строки без перевода из чанкованного не-TTY ввода.
|
|
1077
|
+
if (this.pendingLine) {
|
|
1078
|
+
this.emitLine(this.pendingLine);
|
|
1079
|
+
this.pendingLine = "";
|
|
1080
|
+
}
|
|
973
1081
|
this.listeners.close?.();
|
|
974
1082
|
}
|
|
975
1083
|
}
|