@selesai/code 0.9.15 → 0.10.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 +22 -0
- package/README.md +6 -9
- package/dist/defaults/models.json +2 -2
- package/dist/defaults/settings.json +3 -7
- package/dist/extensions/package.json +2 -4
- package/dist/extensions/pi-zentui/.editorconfig +12 -0
- package/dist/extensions/pi-zentui/.github/CODEOWNERS +1 -0
- package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/bug_report.yml +110 -0
- package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config_question.yml +56 -0
- package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
- package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/rendering_issue.yml +106 -0
- package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/runtime_detection.yml +103 -0
- package/dist/extensions/pi-zentui/.github/dependabot.yml +47 -0
- package/dist/extensions/pi-zentui/.github/pull_request_template.md +34 -0
- package/dist/extensions/pi-zentui/.github/release.yml +18 -0
- package/dist/extensions/pi-zentui/.github/workflows/ci.yml +79 -0
- package/dist/extensions/pi-zentui/.github/workflows/publish.yml +63 -0
- package/dist/extensions/pi-zentui/AGENTS.md +107 -0
- package/dist/extensions/pi-zentui/CONTRIBUTING.md +68 -0
- package/dist/extensions/pi-zentui/LICENSE +21 -0
- package/dist/extensions/pi-zentui/README.md +188 -0
- package/dist/extensions/pi-zentui/SECURITY.md +18 -0
- package/dist/extensions/pi-zentui/assets/main-cover.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/editor-accent-rail.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/editor-minimalist.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode-copy-friendly.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/user-message-compact.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed-copy-friendly.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed.png +0 -0
- package/dist/extensions/pi-zentui/assets/screenshots/user-message-labeled.png +0 -0
- package/dist/extensions/pi-zentui/assets/zentui.png +0 -0
- package/dist/extensions/pi-zentui/biome.json +31 -0
- package/dist/extensions/pi-zentui/docs/configuration.md +509 -0
- package/dist/extensions/pi-zentui/docs/footer-format.md +162 -0
- package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-editor.ts +99 -0
- package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-layout-patch.ts +457 -0
- package/dist/extensions/pi-zentui/extensions/zentui/completion-menu.ts +157 -0
- package/dist/extensions/pi-zentui/extensions/zentui/config.ts +2100 -0
- package/dist/extensions/pi-zentui/extensions/zentui/editor-metadata-format.ts +321 -0
- package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +73 -0
- package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +89 -0
- package/dist/extensions/pi-zentui/extensions/zentui/footer-format.ts +364 -0
- package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +122 -0
- package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +812 -0
- package/dist/extensions/pi-zentui/extensions/zentui/format.ts +568 -0
- package/dist/extensions/pi-zentui/extensions/zentui/git.ts +384 -0
- package/dist/extensions/pi-zentui/extensions/zentui/icons.ts +252 -0
- package/dist/extensions/pi-zentui/extensions/zentui/index.ts +1406 -0
- package/dist/extensions/pi-zentui/extensions/zentui/interaction-summary.ts +1049 -0
- package/dist/extensions/pi-zentui/extensions/zentui/live-context.ts +75 -0
- package/dist/extensions/pi-zentui/extensions/zentui/minimalist-editor.ts +463 -0
- package/dist/extensions/pi-zentui/extensions/zentui/package-version.ts +651 -0
- package/dist/extensions/pi-zentui/extensions/zentui/project-refresh.ts +120 -0
- package/dist/extensions/pi-zentui/extensions/zentui/project-state.ts +59 -0
- package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +166 -0
- package/dist/extensions/pi-zentui/extensions/zentui/repository-root.ts +98 -0
- package/dist/extensions/pi-zentui/extensions/zentui/runtime.ts +841 -0
- package/dist/extensions/pi-zentui/extensions/zentui/selector-border.ts +93 -0
- package/dist/extensions/pi-zentui/extensions/zentui/session-lifecycle.ts +60 -0
- package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +1796 -0
- package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +128 -0
- package/dist/extensions/pi-zentui/extensions/zentui/state.ts +94 -0
- package/dist/extensions/pi-zentui/extensions/zentui/style.ts +424 -0
- package/dist/extensions/pi-zentui/extensions/zentui/telemetry.ts +82 -0
- package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +1290 -0
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +248 -0
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +215 -0
- package/dist/extensions/pi-zentui/extensions/zentui/user-message.ts +206 -0
- package/dist/extensions/pi-zentui/extensions/zentui/working-line-messages.ts +24 -0
- package/dist/extensions/pi-zentui/extensions/zentui/working-line-spinners.ts +54 -0
- package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +1433 -0
- package/dist/extensions/pi-zentui/package-lock.json +5311 -0
- package/dist/extensions/pi-zentui/package.json +60 -0
- package/dist/extensions/pi-zentui/test/accent-rail-editor.test.ts +232 -0
- package/dist/extensions/pi-zentui/test/accent-rail-layout-patch.test.ts +669 -0
- package/dist/extensions/pi-zentui/test/completion-menu.test.ts +265 -0
- package/dist/extensions/pi-zentui/test/config.test.ts +3119 -0
- package/dist/extensions/pi-zentui/test/editor-metadata-format.test.ts +405 -0
- package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +260 -0
- package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +1286 -0
- package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +7196 -0
- package/dist/extensions/pi-zentui/test/extension-status.test.ts +182 -0
- package/dist/extensions/pi-zentui/test/fixtures/starship-nerd-font-symbols.toml +172 -0
- package/dist/extensions/pi-zentui/test/footer-format.test.ts +431 -0
- package/dist/extensions/pi-zentui/test/footer-layout.test.ts +113 -0
- package/dist/extensions/pi-zentui/test/footer-repository-path.test.ts +123 -0
- package/dist/extensions/pi-zentui/test/format.test.ts +865 -0
- package/dist/extensions/pi-zentui/test/git.test.ts +212 -0
- package/dist/extensions/pi-zentui/test/icons.test.ts +69 -0
- package/dist/extensions/pi-zentui/test/interaction-summary.test.ts +1402 -0
- package/dist/extensions/pi-zentui/test/live-context-integration.test.ts +378 -0
- package/dist/extensions/pi-zentui/test/live-context.test.ts +139 -0
- package/dist/extensions/pi-zentui/test/minimalist-editor.test.ts +811 -0
- package/dist/extensions/pi-zentui/test/package-version.test.ts +321 -0
- package/dist/extensions/pi-zentui/test/project-refresh.test.ts +250 -0
- package/dist/extensions/pi-zentui/test/project-state.test.ts +159 -0
- package/dist/extensions/pi-zentui/test/repository-root.test.ts +108 -0
- package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +825 -0
- package/dist/extensions/pi-zentui/test/runtime.test.ts +183 -0
- package/dist/extensions/pi-zentui/test/session-lifecycle.test.ts +40 -0
- package/dist/extensions/pi-zentui/test/settings-command.test.ts +1381 -0
- package/dist/extensions/pi-zentui/test/settings-previews.test.ts +347 -0
- package/dist/extensions/pi-zentui/test/state.test.ts +84 -0
- package/dist/extensions/pi-zentui/test/telemetry-lifecycle-integration.test.ts +276 -0
- package/dist/extensions/pi-zentui/test/telemetry.test.ts +105 -0
- package/dist/extensions/pi-zentui/test/turn-summary-lifecycle.integration.test.ts +327 -0
- package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +164 -0
- package/dist/extensions/pi-zentui/test/user-message-styles.test.ts +292 -0
- package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +730 -0
- package/dist/extensions/pi-zentui/test/working-line-messages.test.ts +34 -0
- package/dist/extensions/pi-zentui/test/working-line.test.ts +1990 -0
- package/dist/extensions/pi-zentui/tsconfig.json +16 -0
- package/dist/extensions/tps.test.ts +38 -0
- package/dist/extensions/{pi-powerline-footer/tps.ts → tps.ts} +4 -0
- package/dist/skills/design-references/SKILL.md +31 -0
- package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
- package/dist/themes/adventure.json +84 -0
- package/dist/themes/adwaita-dark.json +84 -0
- package/dist/themes/arcoiris.json +84 -0
- package/dist/themes/arthur.json +84 -0
- package/dist/themes/atom.json +84 -0
- package/dist/themes/aura.json +84 -0
- package/dist/themes/black-metal-bathory.json +84 -0
- package/dist/themes/black-metal-burzum.json +84 -0
- package/dist/themes/black-metal-khold.json +84 -0
- package/dist/themes/box.json +84 -0
- package/dist/themes/brogrammer.json +84 -0
- package/dist/themes/carbonfox.json +84 -0
- package/dist/themes/catppuccin-mocha.json +84 -0
- package/dist/themes/citruszest.json +84 -0
- package/dist/themes/cursor-dark.json +84 -0
- package/dist/themes/cutie-pro.json +84 -0
- package/dist/themes/dark-modern.json +84 -0
- package/dist/themes/dark-pastel.json +84 -0
- package/dist/themes/dimmed-monokai.json +84 -0
- package/dist/themes/doom-peacock.json +84 -0
- package/dist/themes/dracula-plus.json +84 -0
- package/dist/themes/dracula.json +80 -0
- package/dist/themes/earthsong.json +84 -0
- package/dist/themes/everforest-dark-hard.json +84 -0
- package/dist/themes/everforest-dark.json +80 -0
- package/dist/themes/fahrenheit.json +84 -0
- package/dist/themes/flatland.json +84 -0
- package/dist/themes/flexoki-dark.json +84 -0
- package/dist/themes/front-end-delight.json +84 -0
- package/dist/themes/fun-forrest.json +84 -0
- package/dist/themes/galizur.json +84 -0
- package/dist/themes/github-dark-colorblind.json +84 -0
- package/dist/themes/github-dark-high-contrast.json +84 -0
- package/dist/themes/glacier.json +84 -0
- package/dist/themes/gruber-darker.json +84 -0
- package/dist/themes/gruvbox-dark-hard.json +84 -0
- package/dist/themes/gruvbox-dark.json +84 -0
- package/dist/themes/gruvbox-material.json +84 -0
- package/dist/themes/guezwhoz.json +84 -0
- package/dist/themes/hacktober.json +84 -0
- package/dist/themes/hardcore.json +84 -0
- package/dist/themes/havn-skumring.json +84 -0
- package/dist/themes/ic-orange-ppl.json +84 -0
- package/dist/themes/iterm2-smoooooth.json +84 -0
- package/dist/themes/iterm2-tango-dark.json +84 -0
- package/dist/themes/japanesque.json +84 -0
- package/dist/themes/jellybeans.json +84 -0
- package/dist/themes/kanagawa-wave.json +84 -0
- package/dist/themes/kurokula.json +84 -0
- package/dist/themes/later-this-evening.json +84 -0
- package/dist/themes/lovelace.json +84 -0
- package/dist/themes/material-darker.json +84 -0
- package/dist/themes/matte-black.json +84 -0
- package/dist/themes/mellow.json +84 -0
- package/dist/themes/miasma.json +84 -0
- package/dist/themes/nord.json +80 -0
- package/dist/themes/nvim-dark.json +84 -0
- package/dist/themes/one-dark.json +80 -0
- package/dist/themes/popping-and-locking.json +84 -0
- package/dist/themes/rose-pine.json +80 -0
- package/dist/themes/sea-shells.json +84 -0
- package/dist/themes/sleepy-hollow.json +84 -0
- package/dist/themes/smyck.json +84 -0
- package/dist/themes/solarized-dark.json +80 -0
- package/dist/themes/tokyo-night.json +80 -0
- package/dist/themes/tomorrow-night-bright.json +84 -0
- package/dist/themes/tomorrow-night-burns.json +84 -0
- package/dist/themes/tomorrow-night.json +84 -0
- package/dist/themes/twilight.json +84 -0
- package/dist/themes/vague.json +84 -0
- package/dist/themes/vesper.json +84 -0
- package/dist/themes/xcode-dark-hc.json +84 -0
- package/docs/custom-provider.md +0 -1
- package/package.json +3 -3
- package/dist/extensions/auto-model/auto-model.test.ts +0 -18
- package/dist/extensions/auto-model/classifier.ts +0 -23
- package/dist/extensions/auto-model/config.ts +0 -51
- package/dist/extensions/auto-model/index.ts +0 -22
- package/dist/extensions/auto-model/lifecycle.test.ts +0 -24
- package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
- package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
- package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
- package/dist/extensions/pi-powerline-footer/README.md +0 -513
- package/dist/extensions/pi-powerline-footer/banner.png +0 -0
- package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
- package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
- package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
- package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
- package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
- package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
- package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
- package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
- package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
- package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
- package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
- package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
- package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
- package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
- package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
- package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
- package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
- package/dist/extensions/pi-powerline-footer/package.json +0 -49
- package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
- package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
- package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
- package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
- package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
- package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
- package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
- package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
- package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
- package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
- package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
- package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
- package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
- package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
- package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
- package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
- package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
- package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
- package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
- package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
- package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
- package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
- package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
- package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
- package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
- package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
- package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
- package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
- package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
- package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
- package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
- package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
- package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
- package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
- package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
- package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
- package/dist/extensions/pi-powerline-footer/theme.json +0 -12
- package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
- package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
- package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
- package/dist/extensions/pi-powerline-footer/types.ts +0 -235
- package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
- package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
- package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
- package/dist/extensions/preview-tools-disabled.test.ts +0 -37
- package/dist/extensions/preview-tools-disabled.ts +0 -11
- package/dist/themes/powerline-footer/theme.json +0 -33
|
@@ -1,1905 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { appendProjectHistory, matchHistoryEntries, readGlobalShellHistory } from "../bash-mode/history.ts";
|
|
7
|
-
import { BashTranscriptStore } from "../bash-mode/transcript.ts";
|
|
8
|
-
import {
|
|
9
|
-
BashAutocompleteProvider,
|
|
10
|
-
BashCompletionEngine,
|
|
11
|
-
getOneOffBashCommandContext,
|
|
12
|
-
ModeAwareAutocompleteProvider,
|
|
13
|
-
OneOffBashAutocompleteProvider,
|
|
14
|
-
} from "../bash-mode/completion.ts";
|
|
15
|
-
import { getIcons } from "../icons.ts";
|
|
16
|
-
import { resolveColor } from "../theme.ts";
|
|
17
|
-
import { ManagedShellSession } from "../bash-mode/shell-session.ts";
|
|
18
|
-
|
|
19
|
-
function getMethod(target: object, name: string): Function {
|
|
20
|
-
const method = Reflect.get(target, name);
|
|
21
|
-
if (typeof method !== "function") {
|
|
22
|
-
throw new Error(`Expected ${name} to be a function`);
|
|
23
|
-
}
|
|
24
|
-
return method;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function resolveManagedShellPath(): string | null {
|
|
28
|
-
for (const shellPath of ["/bin/zsh", "/bin/bash"]) {
|
|
29
|
-
if (existsSync(shellPath)) return shellPath;
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function ensureEditorModuleLinks(): { cleanup: () => void } {
|
|
35
|
-
const nodeModulesDir = join(process.cwd(), "node_modules", "@earendil-works");
|
|
36
|
-
mkdirSync(nodeModulesDir, { recursive: true });
|
|
37
|
-
const links = [
|
|
38
|
-
{
|
|
39
|
-
link: join(nodeModulesDir, "pi-coding-agent"),
|
|
40
|
-
target: "/opt/homebrew/lib/node_modules/@selesai/code",
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
link: join(nodeModulesDir, "pi-tui"),
|
|
44
|
-
target: "/opt/homebrew/lib/node_modules/@selesai/code/node_modules/@earendil-works/pi-tui",
|
|
45
|
-
},
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
const createdLinks: string[] = [];
|
|
49
|
-
for (const { link, target } of links) {
|
|
50
|
-
if (!existsSync(link)) {
|
|
51
|
-
symlinkSync(target, link);
|
|
52
|
-
createdLinks.push(link);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
cleanup() {
|
|
58
|
-
for (const link of createdLinks.reverse()) {
|
|
59
|
-
if (existsSync(link)) {
|
|
60
|
-
rmSync(link, { recursive: true, force: true });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
test("project history is stored newest-first and global zsh history parses histfile format", () => {
|
|
68
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-history-"));
|
|
69
|
-
const histfile = join(cwd, ".zsh_history");
|
|
70
|
-
process.env.HISTFILE = histfile;
|
|
71
|
-
|
|
72
|
-
appendProjectHistory(cwd, "git status", cwd);
|
|
73
|
-
appendProjectHistory(cwd, "git stash", cwd);
|
|
74
|
-
appendProjectHistory(cwd, "git status", cwd);
|
|
75
|
-
|
|
76
|
-
writeFileSync(histfile, [
|
|
77
|
-
": 1711111111:0;git fetch",
|
|
78
|
-
": 1711111112:0;git pull",
|
|
79
|
-
"plain-command",
|
|
80
|
-
"",
|
|
81
|
-
].join("\n"));
|
|
82
|
-
|
|
83
|
-
const global = readGlobalShellHistory("/bin/zsh");
|
|
84
|
-
assert.deepEqual(global, ["plain-command", "git pull", "git fetch"]);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("global history caches an unreadable file until its fingerprint changes", () => {
|
|
88
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-unreadable-history-"));
|
|
89
|
-
const historyPath = join(cwd, ".zsh_history");
|
|
90
|
-
const originalHistfile = process.env.HISTFILE;
|
|
91
|
-
const originalDebug = console.debug;
|
|
92
|
-
let debugCalls = 0;
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
mkdirSync(historyPath);
|
|
96
|
-
process.env.HISTFILE = historyPath;
|
|
97
|
-
console.debug = () => {
|
|
98
|
-
debugCalls += 1;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
assert.deepEqual(readGlobalShellHistory("/bin/zsh"), []);
|
|
102
|
-
assert.deepEqual(readGlobalShellHistory("/bin/zsh"), []);
|
|
103
|
-
assert.equal(debugCalls, 1);
|
|
104
|
-
|
|
105
|
-
rmSync(historyPath, { recursive: true });
|
|
106
|
-
writeFileSync(historyPath, ": 1711111111:0;git status\n");
|
|
107
|
-
assert.deepEqual(readGlobalShellHistory("/bin/zsh"), ["git status"]);
|
|
108
|
-
} finally {
|
|
109
|
-
console.debug = originalDebug;
|
|
110
|
-
if (originalHistfile === undefined) {
|
|
111
|
-
delete process.env.HISTFILE;
|
|
112
|
-
} else {
|
|
113
|
-
process.env.HISTFILE = originalHistfile;
|
|
114
|
-
}
|
|
115
|
-
rmSync(cwd, { recursive: true, force: true });
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("matchHistoryEntries returns newest entries when the prefix is empty", () => {
|
|
120
|
-
const matches = matchHistoryEntries([
|
|
121
|
-
"git stash",
|
|
122
|
-
"git status",
|
|
123
|
-
"git stash",
|
|
124
|
-
"git fetch",
|
|
125
|
-
], "", 10);
|
|
126
|
-
|
|
127
|
-
assert.deepEqual(matches, ["git stash", "git status", "git fetch"]);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("theme.json can override icons without touching colors", () => {
|
|
131
|
-
const themePath = join(process.cwd(), "theme.json");
|
|
132
|
-
const originalTheme = existsSync(themePath) ? readFileSync(themePath, "utf8") : null;
|
|
133
|
-
const originalNerdFonts = process.env.POWERLINE_NERD_FONTS;
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
writeFileSync(themePath, JSON.stringify({ icons: { auto: "↯", warning: "" } }, null, 2) + "\n");
|
|
137
|
-
process.env.POWERLINE_NERD_FONTS = "0";
|
|
138
|
-
|
|
139
|
-
const icons = getIcons();
|
|
140
|
-
assert.equal(icons.auto, "↯");
|
|
141
|
-
assert.equal(icons.warning, "");
|
|
142
|
-
assert.equal(icons.folder, "dir");
|
|
143
|
-
} finally {
|
|
144
|
-
if (originalTheme === null) {
|
|
145
|
-
if (existsSync(themePath)) unlinkSync(themePath);
|
|
146
|
-
} else {
|
|
147
|
-
writeFileSync(themePath, originalTheme);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (originalNerdFonts === undefined) {
|
|
151
|
-
delete process.env.POWERLINE_NERD_FONTS;
|
|
152
|
-
} else {
|
|
153
|
-
process.env.POWERLINE_NERD_FONTS = originalNerdFonts;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test("theme.json loads from the documented agent extension path", () => {
|
|
159
|
-
const originalAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
160
|
-
const agentDir = mkdtempSync(join(tmpdir(), "powerline-theme-"));
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
const themeDir = join(agentDir, "extensions", "powerline-footer");
|
|
164
|
-
mkdirSync(themeDir, { recursive: true });
|
|
165
|
-
writeFileSync(join(themeDir, "theme.json"), JSON.stringify({ colors: { model: "#ff5500", path: "#ff5500" } }));
|
|
166
|
-
process.env.SELESAI_CODING_AGENT_DIR = agentDir;
|
|
167
|
-
|
|
168
|
-
assert.equal(resolveColor("model"), "#ff5500");
|
|
169
|
-
assert.equal(resolveColor("path"), "#ff5500");
|
|
170
|
-
} finally {
|
|
171
|
-
if (originalAgentDir === undefined) {
|
|
172
|
-
delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
173
|
-
} else {
|
|
174
|
-
process.env.SELESAI_CODING_AGENT_DIR = originalAgentDir;
|
|
175
|
-
}
|
|
176
|
-
rmSync(agentDir, { recursive: true, force: true });
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("one-off bash command context strips ! and !! prefixes", () => {
|
|
181
|
-
assert.deepEqual(getOneOffBashCommandContext("!git status"), {
|
|
182
|
-
prefix: "!",
|
|
183
|
-
command: "git status",
|
|
184
|
-
offset: 1,
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
assert.deepEqual(getOneOffBashCommandContext("!!git status"), {
|
|
188
|
-
prefix: "!!",
|
|
189
|
-
command: "git status",
|
|
190
|
-
offset: 2,
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
assert.equal(getOneOffBashCommandContext(" !!git status"), null);
|
|
194
|
-
assert.equal(getOneOffBashCommandContext("git status"), null);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("transcript store truncates oldest commands at command boundaries", () => {
|
|
198
|
-
const store = new BashTranscriptStore({ transcriptMaxLines: 3, transcriptMaxBytes: 1024 });
|
|
199
|
-
store.startCommand("a", "echo one", "/tmp");
|
|
200
|
-
store.appendOutput("a", "line-1\nline-2");
|
|
201
|
-
store.finishCommand("a", 0);
|
|
202
|
-
|
|
203
|
-
store.startCommand("b", "echo two", "/tmp");
|
|
204
|
-
store.appendOutput("b", "line-3\nline-4");
|
|
205
|
-
store.finishCommand("b", 0);
|
|
206
|
-
|
|
207
|
-
const snapshot = store.getSnapshot();
|
|
208
|
-
assert.equal(snapshot.commands.length, 1);
|
|
209
|
-
assert.equal(snapshot.commands[0]?.id, "b");
|
|
210
|
-
assert.equal(snapshot.truncatedCommands, 1);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
test("transcript store keeps the active command even when it alone exceeds limits", () => {
|
|
214
|
-
const store = new BashTranscriptStore({ transcriptMaxLines: 3, transcriptMaxBytes: 1024 });
|
|
215
|
-
store.startCommand("a", "echo big", "/tmp");
|
|
216
|
-
store.appendOutput("a", "1\n2\n3\n4");
|
|
217
|
-
|
|
218
|
-
const snapshot = store.getSnapshot();
|
|
219
|
-
assert.equal(snapshot.commands.length, 1);
|
|
220
|
-
assert.equal(snapshot.commands[0]?.id, "a");
|
|
221
|
-
assert.deepEqual(snapshot.commands[0]?.output, ["1", "2", "3", "4"]);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test("ghost suggestion prefers project history over global history", async () => {
|
|
225
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-ghost-"));
|
|
226
|
-
const histfile = join(cwd, ".zsh_history");
|
|
227
|
-
process.env.HISTFILE = histfile;
|
|
228
|
-
writeFileSync(histfile, ": 1711111111:0;git switch\n");
|
|
229
|
-
appendProjectHistory(cwd, "git status", cwd);
|
|
230
|
-
appendProjectHistory(cwd, "git stash", cwd);
|
|
231
|
-
|
|
232
|
-
const engine = new BashCompletionEngine();
|
|
233
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
234
|
-
"git st",
|
|
235
|
-
cwd,
|
|
236
|
-
"/bin/zsh",
|
|
237
|
-
new AbortController().signal,
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
assert.equal(suggestion?.value, "git stash");
|
|
241
|
-
assert.equal(suggestion?.source, "project-history");
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test("ghost suggestion shows newest project history on an empty prompt", async () => {
|
|
245
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-empty-project-ghost-"));
|
|
246
|
-
const histfile = join(cwd, ".zsh_history");
|
|
247
|
-
process.env.HISTFILE = histfile;
|
|
248
|
-
writeFileSync(histfile, ": 1711111111:0;git pull\n");
|
|
249
|
-
appendProjectHistory(cwd, "git status", cwd);
|
|
250
|
-
appendProjectHistory(cwd, "git stash", cwd);
|
|
251
|
-
|
|
252
|
-
const engine = new BashCompletionEngine();
|
|
253
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
254
|
-
"",
|
|
255
|
-
cwd,
|
|
256
|
-
"/bin/zsh",
|
|
257
|
-
new AbortController().signal,
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
assert.equal(suggestion?.value, "git stash");
|
|
261
|
-
assert.equal(suggestion?.source, "project-history");
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
test("ghost suggestion stays empty on an empty prompt when only global history exists", async () => {
|
|
265
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-empty-global-ghost-"));
|
|
266
|
-
const histfile = join(cwd, ".zsh_history");
|
|
267
|
-
process.env.HISTFILE = histfile;
|
|
268
|
-
writeFileSync(histfile, [
|
|
269
|
-
": 1711111111:0;git fetch",
|
|
270
|
-
": 1711111112:0;git pull",
|
|
271
|
-
].join("\n"));
|
|
272
|
-
|
|
273
|
-
const engine = new BashCompletionEngine();
|
|
274
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
275
|
-
"",
|
|
276
|
-
cwd,
|
|
277
|
-
"/bin/zsh",
|
|
278
|
-
new AbortController().signal,
|
|
279
|
-
);
|
|
280
|
-
|
|
281
|
-
assert.equal(suggestion, null);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
test("ghost suggestion stays empty when the prompt is empty and no history exists", async () => {
|
|
285
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-empty-no-history-"));
|
|
286
|
-
const histfile = join(cwd, ".zsh_history");
|
|
287
|
-
process.env.HISTFILE = histfile;
|
|
288
|
-
writeFileSync(histfile, "");
|
|
289
|
-
|
|
290
|
-
const engine = new BashCompletionEngine();
|
|
291
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
292
|
-
"",
|
|
293
|
-
cwd,
|
|
294
|
-
"/bin/zsh",
|
|
295
|
-
new AbortController().signal,
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
assert.equal(suggestion, null);
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
test("ghost suggestion can extend the current token from deterministic path completions", async () => {
|
|
302
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-inline-ghost-"));
|
|
303
|
-
mkdirSync(join(cwd, "dev"), { recursive: true });
|
|
304
|
-
mkdirSync(join(cwd, "My Folder"), { recursive: true });
|
|
305
|
-
|
|
306
|
-
const engine = new BashCompletionEngine();
|
|
307
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
308
|
-
"cd d",
|
|
309
|
-
cwd,
|
|
310
|
-
"/bin/sh",
|
|
311
|
-
new AbortController().signal,
|
|
312
|
-
);
|
|
313
|
-
const escapedSuggestion = await engine.getGhostSuggestion(
|
|
314
|
-
"cd M",
|
|
315
|
-
cwd,
|
|
316
|
-
"/bin/sh",
|
|
317
|
-
new AbortController().signal,
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
assert.equal(suggestion?.value, "cd dev/");
|
|
321
|
-
assert.equal(suggestion?.source, "path");
|
|
322
|
-
assert.equal(escapedSuggestion?.value, "cd My\\ Folder/");
|
|
323
|
-
assert.equal(escapedSuggestion?.source, "path");
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test("ghost suggestion does not invoke shell-native completion hooks", async () => {
|
|
327
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-no-native-ghost-"));
|
|
328
|
-
mkdirSync(join(cwd, "dev"), { recursive: true });
|
|
329
|
-
|
|
330
|
-
const engine = new BashCompletionEngine();
|
|
331
|
-
Reflect.set(engine, "getNativeSuggestions", async () => {
|
|
332
|
-
throw new Error("native completion should stay disabled");
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
336
|
-
"cd d",
|
|
337
|
-
cwd,
|
|
338
|
-
"/bin/zsh",
|
|
339
|
-
new AbortController().signal,
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
assert.equal(suggestion?.value, "cd dev/");
|
|
343
|
-
assert.equal(suggestion?.source, "path");
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
test("command-position ghost prefers the newest successful project-history command", async () => {
|
|
347
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-command-project-history-"));
|
|
348
|
-
const histfile = join(cwd, ".zsh_history");
|
|
349
|
-
process.env.HISTFILE = histfile;
|
|
350
|
-
writeFileSync(histfile, "");
|
|
351
|
-
appendProjectHistory(cwd, "git status", cwd);
|
|
352
|
-
|
|
353
|
-
const engine = new BashCompletionEngine();
|
|
354
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
355
|
-
"g",
|
|
356
|
-
cwd,
|
|
357
|
-
"/bin/zsh",
|
|
358
|
-
new AbortController().signal,
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
assert.equal(suggestion?.value, "git status");
|
|
362
|
-
assert.equal(suggestion?.source, "project-history");
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
test("command-position ghost uses guarded global git history when project history is absent", async () => {
|
|
366
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-command-global-history-"));
|
|
367
|
-
const histfile = join(cwd, ".zsh_history");
|
|
368
|
-
process.env.HISTFILE = histfile;
|
|
369
|
-
writeFileSync(histfile, ": 1711111111:0;git stash\n");
|
|
370
|
-
|
|
371
|
-
const engine = new BashCompletionEngine();
|
|
372
|
-
const shortStemSuggestion = await engine.getGhostSuggestion(
|
|
373
|
-
"g",
|
|
374
|
-
cwd,
|
|
375
|
-
"/bin/zsh",
|
|
376
|
-
new AbortController().signal,
|
|
377
|
-
);
|
|
378
|
-
const guardedSuggestion = await engine.getGhostSuggestion(
|
|
379
|
-
"gi",
|
|
380
|
-
cwd,
|
|
381
|
-
"/bin/zsh",
|
|
382
|
-
new AbortController().signal,
|
|
383
|
-
);
|
|
384
|
-
|
|
385
|
-
assert.equal(shortStemSuggestion?.value, "git status");
|
|
386
|
-
assert.equal(shortStemSuggestion?.source, "git");
|
|
387
|
-
assert.equal(guardedSuggestion?.value, "git stash");
|
|
388
|
-
assert.equal(guardedSuggestion?.source, "global-history");
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
test("command-position ghost falls back to git status when git is likely but history is absent", async () => {
|
|
392
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-command-git-default-"));
|
|
393
|
-
const histfile = join(cwd, ".zsh_history");
|
|
394
|
-
process.env.HISTFILE = histfile;
|
|
395
|
-
writeFileSync(histfile, "");
|
|
396
|
-
|
|
397
|
-
const engine = new BashCompletionEngine();
|
|
398
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
399
|
-
"g",
|
|
400
|
-
cwd,
|
|
401
|
-
"/bin/zsh",
|
|
402
|
-
new AbortController().signal,
|
|
403
|
-
);
|
|
404
|
-
|
|
405
|
-
assert.equal(suggestion?.value, "git status");
|
|
406
|
-
assert.equal(suggestion?.source, "git");
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
test("command-position ghost falls back to cd dot-dot for the cd stem", async () => {
|
|
410
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-command-cd-default-"));
|
|
411
|
-
const histfile = join(cwd, ".zsh_history");
|
|
412
|
-
process.env.HISTFILE = histfile;
|
|
413
|
-
writeFileSync(histfile, "");
|
|
414
|
-
|
|
415
|
-
const engine = new BashCompletionEngine();
|
|
416
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
417
|
-
"c",
|
|
418
|
-
cwd,
|
|
419
|
-
"/bin/zsh",
|
|
420
|
-
new AbortController().signal,
|
|
421
|
-
);
|
|
422
|
-
|
|
423
|
-
assert.equal(suggestion?.value, "cd ..");
|
|
424
|
-
assert.equal(suggestion?.source, "path");
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
test("command-position ghost stays empty when there is no supported history-backed stem", async () => {
|
|
428
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-command-empty-"));
|
|
429
|
-
const histfile = join(cwd, ".zsh_history");
|
|
430
|
-
process.env.HISTFILE = histfile;
|
|
431
|
-
writeFileSync(histfile, "");
|
|
432
|
-
|
|
433
|
-
const engine = new BashCompletionEngine();
|
|
434
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
435
|
-
"x",
|
|
436
|
-
cwd,
|
|
437
|
-
"/bin/zsh",
|
|
438
|
-
new AbortController().signal,
|
|
439
|
-
);
|
|
440
|
-
|
|
441
|
-
assert.equal(suggestion, null);
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
test("ghost suggestion ignores invalid raw global history and keeps a deterministic git candidate", async () => {
|
|
445
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-global-history-ghost-"));
|
|
446
|
-
const histfile = join(cwd, ".zsh_history");
|
|
447
|
-
process.env.HISTFILE = histfile;
|
|
448
|
-
writeFileSync(histfile, ": 1711111111:0;git statis\n");
|
|
449
|
-
|
|
450
|
-
const engine = new BashCompletionEngine();
|
|
451
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
452
|
-
"git st",
|
|
453
|
-
cwd,
|
|
454
|
-
"/bin/zsh",
|
|
455
|
-
new AbortController().signal,
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
assert.match(suggestion?.value ?? "", /^git sta(?:sh|tus)$/);
|
|
459
|
-
assert.equal(suggestion?.source, "git");
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
test("global history boosts already-valid deterministic git candidates", async () => {
|
|
463
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-global-history-tiebreak-ghost-"));
|
|
464
|
-
const histfile = join(cwd, ".zsh_history");
|
|
465
|
-
process.env.HISTFILE = histfile;
|
|
466
|
-
writeFileSync(histfile, ": 1711111111:0;git stash\n");
|
|
467
|
-
|
|
468
|
-
const engine = new BashCompletionEngine();
|
|
469
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
470
|
-
"git st",
|
|
471
|
-
cwd,
|
|
472
|
-
"/bin/zsh",
|
|
473
|
-
new AbortController().signal,
|
|
474
|
-
);
|
|
475
|
-
|
|
476
|
-
assert.equal(suggestion?.value, "git stash");
|
|
477
|
-
assert.equal(suggestion?.source, "git");
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
test("deterministic path completion keeps directory suffixes for escaped paths", async () => {
|
|
481
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-path-escaped-"));
|
|
482
|
-
const histfile = join(cwd, ".zsh_history");
|
|
483
|
-
process.env.HISTFILE = histfile;
|
|
484
|
-
writeFileSync(histfile, "");
|
|
485
|
-
mkdirSync(join(cwd, "My Folder"), { recursive: true });
|
|
486
|
-
|
|
487
|
-
const engine = new BashCompletionEngine();
|
|
488
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
489
|
-
"cd M",
|
|
490
|
-
cwd,
|
|
491
|
-
"/bin/zsh",
|
|
492
|
-
new AbortController().signal,
|
|
493
|
-
);
|
|
494
|
-
|
|
495
|
-
assert.equal(suggestion?.value, "cd My\\ Folder/");
|
|
496
|
-
assert.equal(suggestion?.source, "path");
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
test("deterministic path completion handles bash argument position", async () => {
|
|
500
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-bash-path-"));
|
|
501
|
-
mkdirSync(join(cwd, "devdir"), { recursive: true });
|
|
502
|
-
|
|
503
|
-
const engine = new BashCompletionEngine();
|
|
504
|
-
const suggestion = await engine.getGhostSuggestion(
|
|
505
|
-
"cd d",
|
|
506
|
-
cwd,
|
|
507
|
-
"/bin/bash",
|
|
508
|
-
new AbortController().signal,
|
|
509
|
-
);
|
|
510
|
-
|
|
511
|
-
assert.equal(suggestion?.value, "cd devdir/");
|
|
512
|
-
assert.equal(suggestion?.source, "path");
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
test("managed shell session preserves cwd changes across commands", async (t) => {
|
|
516
|
-
const shellPath = resolveManagedShellPath();
|
|
517
|
-
if (!shellPath) {
|
|
518
|
-
t.skip("requires zsh or bash");
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-shell-"));
|
|
523
|
-
const childDir = join(cwd, "child");
|
|
524
|
-
mkdirSync(childDir, { recursive: true });
|
|
525
|
-
const store = new BashTranscriptStore({ transcriptMaxLines: 100, transcriptMaxBytes: 64 * 1024 });
|
|
526
|
-
const session = new ManagedShellSession(shellPath, cwd, store, () => {}, () => {});
|
|
527
|
-
|
|
528
|
-
try {
|
|
529
|
-
await session.ensureReady();
|
|
530
|
-
await session.runCommand(`cd ${childDir}`);
|
|
531
|
-
const waitForCommand = async () => {
|
|
532
|
-
const start = Date.now();
|
|
533
|
-
while (session.state.running && Date.now() - start < 5000) {
|
|
534
|
-
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
535
|
-
}
|
|
536
|
-
assert.equal(session.state.running, false);
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
await waitForCommand();
|
|
540
|
-
assert.equal(session.state.cwd, childDir);
|
|
541
|
-
|
|
542
|
-
await session.runCommand("pwd");
|
|
543
|
-
await waitForCommand();
|
|
544
|
-
|
|
545
|
-
const snapshot = store.getSnapshot();
|
|
546
|
-
const lastCommand = snapshot.commands[snapshot.commands.length - 1];
|
|
547
|
-
assert.ok(lastCommand?.output.includes(childDir));
|
|
548
|
-
} finally {
|
|
549
|
-
session.dispose();
|
|
550
|
-
}
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
test("managed shell session recovers cleanly after interrupt", async (t) => {
|
|
554
|
-
const shellPath = resolveManagedShellPath();
|
|
555
|
-
if (!shellPath) {
|
|
556
|
-
t.skip("requires zsh or bash");
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
const cwd = mkdtempSync(join(tmpdir(), "powerline-shell-interrupt-"));
|
|
561
|
-
const store = new BashTranscriptStore({ transcriptMaxLines: 100, transcriptMaxBytes: 64 * 1024 });
|
|
562
|
-
const session = new ManagedShellSession(shellPath, cwd, store, () => {}, () => {});
|
|
563
|
-
|
|
564
|
-
const waitForCommand = async () => {
|
|
565
|
-
const start = Date.now();
|
|
566
|
-
while (session.state.running && Date.now() - start < 5000) {
|
|
567
|
-
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
568
|
-
}
|
|
569
|
-
assert.equal(session.state.running, false);
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
try {
|
|
573
|
-
await session.ensureReady();
|
|
574
|
-
await session.runCommand("sleep 5");
|
|
575
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
576
|
-
session.interrupt();
|
|
577
|
-
await waitForCommand();
|
|
578
|
-
|
|
579
|
-
const interruptedCommand = store.getSnapshot().commands[0];
|
|
580
|
-
assert.equal(interruptedCommand?.exitCode, 130);
|
|
581
|
-
|
|
582
|
-
await session.runCommand("printf 'after\\n'");
|
|
583
|
-
await waitForCommand();
|
|
584
|
-
|
|
585
|
-
const snapshot = store.getSnapshot();
|
|
586
|
-
const lastCommand = snapshot.commands[snapshot.commands.length - 1];
|
|
587
|
-
assert.equal(lastCommand?.command, "printf 'after\\n'");
|
|
588
|
-
assert.equal(lastCommand?.exitCode, 0);
|
|
589
|
-
assert.ok(lastCommand?.output.includes("after"));
|
|
590
|
-
} finally {
|
|
591
|
-
session.dispose();
|
|
592
|
-
}
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
test("bash editor Tab accepts the current ghost suggestion without opening autocomplete", async () => {
|
|
596
|
-
const links = ensureEditorModuleLinks();
|
|
597
|
-
|
|
598
|
-
try {
|
|
599
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
600
|
-
let accepted = false;
|
|
601
|
-
|
|
602
|
-
getMethod(BashModeEditor.prototype, "handleInput").call({
|
|
603
|
-
optionsRef: {
|
|
604
|
-
isBashModeActive: () => true,
|
|
605
|
-
isShellRunning: () => false,
|
|
606
|
-
onExitBashMode() {},
|
|
607
|
-
onInterrupt() {},
|
|
608
|
-
onNotify() {},
|
|
609
|
-
onSubmitCommand() {},
|
|
610
|
-
},
|
|
611
|
-
keybindingsRef: {
|
|
612
|
-
matches(_data: string, id: string) {
|
|
613
|
-
return id === "tui.input.tab";
|
|
614
|
-
},
|
|
615
|
-
},
|
|
616
|
-
isShowingAutocomplete() {
|
|
617
|
-
return false;
|
|
618
|
-
},
|
|
619
|
-
acceptGhostSuggestion() {
|
|
620
|
-
accepted = true;
|
|
621
|
-
return true;
|
|
622
|
-
},
|
|
623
|
-
}, "tab");
|
|
624
|
-
|
|
625
|
-
assert.equal(accepted, true);
|
|
626
|
-
} finally {
|
|
627
|
-
links.cleanup();
|
|
628
|
-
}
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
test("bash editor does not submit pasted multiline input while bracketed paste is active", async () => {
|
|
632
|
-
const links = ensureEditorModuleLinks();
|
|
633
|
-
|
|
634
|
-
try {
|
|
635
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
636
|
-
const { CustomEditor } = await import(new URL("../node_modules/@selesai/code/dist/modes/interactive/components/custom-editor.js", import.meta.url).href);
|
|
637
|
-
|
|
638
|
-
let delegated = 0;
|
|
639
|
-
let submitted = 0;
|
|
640
|
-
const superHandleInput = CustomEditor.prototype.handleInput;
|
|
641
|
-
CustomEditor.prototype.handleInput = function handleInput() {
|
|
642
|
-
delegated += 1;
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
try {
|
|
646
|
-
getMethod(BashModeEditor.prototype, "handleInput").call({
|
|
647
|
-
isInPaste: true,
|
|
648
|
-
optionsRef: {
|
|
649
|
-
isBashModeActive: () => true,
|
|
650
|
-
isShellRunning: () => false,
|
|
651
|
-
onExitBashMode() {},
|
|
652
|
-
onInterrupt() {},
|
|
653
|
-
onNotify() {},
|
|
654
|
-
onSubmitCommand() {
|
|
655
|
-
submitted += 1;
|
|
656
|
-
},
|
|
657
|
-
getHistoryEntries() {
|
|
658
|
-
return [];
|
|
659
|
-
},
|
|
660
|
-
resolveGhostSuggestion: async () => null,
|
|
661
|
-
},
|
|
662
|
-
keybindingsRef: {
|
|
663
|
-
matches(data: string, id: string) {
|
|
664
|
-
return data === "\r" && id === "tui.input.submit";
|
|
665
|
-
},
|
|
666
|
-
},
|
|
667
|
-
}, "\r");
|
|
668
|
-
} finally {
|
|
669
|
-
CustomEditor.prototype.handleInput = superHandleInput;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
assert.equal(submitted, 0);
|
|
673
|
-
assert.equal(delegated, 1);
|
|
674
|
-
} finally {
|
|
675
|
-
links.cleanup();
|
|
676
|
-
}
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
test("bash editor refreshes shell ghost state after a bracketed paste completes", async () => {
|
|
680
|
-
const links = ensureEditorModuleLinks();
|
|
681
|
-
|
|
682
|
-
try {
|
|
683
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
684
|
-
const { CustomEditor } = await import(new URL("../node_modules/@selesai/code/dist/modes/interactive/components/custom-editor.js", import.meta.url).href);
|
|
685
|
-
|
|
686
|
-
let delegated = 0;
|
|
687
|
-
let scheduled = 0;
|
|
688
|
-
const superHandleInput = CustomEditor.prototype.handleInput;
|
|
689
|
-
CustomEditor.prototype.handleInput = function handleInput() {
|
|
690
|
-
delegated += 1;
|
|
691
|
-
Reflect.set(this, "isInPaste", false);
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
try {
|
|
695
|
-
getMethod(BashModeEditor.prototype, "handleInput").call({
|
|
696
|
-
isInPaste: true,
|
|
697
|
-
optionsRef: {
|
|
698
|
-
isBashModeActive: () => true,
|
|
699
|
-
isShellRunning: () => false,
|
|
700
|
-
onExitBashMode() {},
|
|
701
|
-
onInterrupt() {},
|
|
702
|
-
onNotify() {},
|
|
703
|
-
onSubmitCommand() {},
|
|
704
|
-
getHistoryEntries() {
|
|
705
|
-
return [];
|
|
706
|
-
},
|
|
707
|
-
resolveGhostSuggestion: async () => null,
|
|
708
|
-
},
|
|
709
|
-
keybindingsRef: {
|
|
710
|
-
matches() {
|
|
711
|
-
return false;
|
|
712
|
-
},
|
|
713
|
-
},
|
|
714
|
-
getExpandedText() {
|
|
715
|
-
return "git status";
|
|
716
|
-
},
|
|
717
|
-
isShellCompletionContext() {
|
|
718
|
-
return true;
|
|
719
|
-
},
|
|
720
|
-
shellHistoryIndex: 3,
|
|
721
|
-
shellHistoryItems: ["git status"],
|
|
722
|
-
shellHistoryDraft: "git",
|
|
723
|
-
scheduleGhostUpdate() {
|
|
724
|
-
scheduled += 1;
|
|
725
|
-
},
|
|
726
|
-
}, "\r");
|
|
727
|
-
} finally {
|
|
728
|
-
CustomEditor.prototype.handleInput = superHandleInput;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
assert.equal(delegated, 1);
|
|
732
|
-
assert.equal(scheduled, 1);
|
|
733
|
-
} finally {
|
|
734
|
-
links.cleanup();
|
|
735
|
-
}
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
test("bash editor inserts Finder file drops as path strings", async (t) => {
|
|
739
|
-
if (process.platform === "win32") {
|
|
740
|
-
t.skip("Finder file drops are macOS/POSIX paths");
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
const links = ensureEditorModuleLinks();
|
|
745
|
-
|
|
746
|
-
try {
|
|
747
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
748
|
-
const { KeybindingsManager } = await import(new URL("../node_modules/@selesai/code/dist/core/keybindings.js", import.meta.url).href);
|
|
749
|
-
const keybindings = KeybindingsManager.create();
|
|
750
|
-
let scheduled = 0;
|
|
751
|
-
const editor = new BashModeEditor(
|
|
752
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
753
|
-
{},
|
|
754
|
-
keybindings,
|
|
755
|
-
{
|
|
756
|
-
keybindings,
|
|
757
|
-
isBashModeActive: () => false,
|
|
758
|
-
isShellRunning: () => false,
|
|
759
|
-
onExitBashMode() {},
|
|
760
|
-
onSubmitCommand() {},
|
|
761
|
-
onInterrupt() {},
|
|
762
|
-
onNotify() {},
|
|
763
|
-
getHistoryEntries: () => [],
|
|
764
|
-
resolveGhostSuggestion: async () => null,
|
|
765
|
-
},
|
|
766
|
-
);
|
|
767
|
-
|
|
768
|
-
editor.handleInput("\x1b[200~file:///Users/nico/Desktop/Screen%20Shot%202026-05-08.png\x1b[201~");
|
|
769
|
-
assert.equal(editor.getText(), "/Users/nico/Desktop/Screen Shot 2026-05-08.png");
|
|
770
|
-
|
|
771
|
-
editor.handleInput(" ");
|
|
772
|
-
editor.handleInput("\x1b[200~/Users/nico/Documents/Project\\ Folder\x1b[201~");
|
|
773
|
-
assert.equal(editor.getText(), "/Users/nico/Desktop/Screen Shot 2026-05-08.png /Users/nico/Documents/Project\\ Folder");
|
|
774
|
-
|
|
775
|
-
const shellEditor = new BashModeEditor(
|
|
776
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
777
|
-
{},
|
|
778
|
-
keybindings,
|
|
779
|
-
{
|
|
780
|
-
keybindings,
|
|
781
|
-
isBashModeActive: () => true,
|
|
782
|
-
isShellRunning: () => false,
|
|
783
|
-
onExitBashMode() {},
|
|
784
|
-
onSubmitCommand() {},
|
|
785
|
-
onInterrupt() {},
|
|
786
|
-
onNotify() {},
|
|
787
|
-
getHistoryEntries: () => [],
|
|
788
|
-
resolveGhostSuggestion: async () => null,
|
|
789
|
-
},
|
|
790
|
-
);
|
|
791
|
-
Reflect.set(shellEditor, "scheduleGhostUpdate", () => {
|
|
792
|
-
scheduled += 1;
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
shellEditor.handleInput("\x1b[200~file:///Users/nico/Pictures/Finder%20Image.png\nfile:///Users/nico/Desktop/Capture.png\x1b[201~");
|
|
796
|
-
assert.equal(shellEditor.getText(), "/Users/nico/Pictures/Finder Image.png /Users/nico/Desktop/Capture.png");
|
|
797
|
-
assert.equal(scheduled, 1);
|
|
798
|
-
} finally {
|
|
799
|
-
links.cleanup();
|
|
800
|
-
}
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
test("one-off bash autocomplete provider stays inactive even inside bang commands", async () => {
|
|
804
|
-
const provider = new OneOffBashAutocompleteProvider();
|
|
805
|
-
const suggestions = await provider.getSuggestions(
|
|
806
|
-
["!!gi"],
|
|
807
|
-
0,
|
|
808
|
-
4,
|
|
809
|
-
{ signal: new AbortController().signal },
|
|
810
|
-
);
|
|
811
|
-
|
|
812
|
-
assert.equal(suggestions, null);
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
test("bash autocomplete providers return null in shell contexts", async () => {
|
|
816
|
-
const signal = new AbortController().signal;
|
|
817
|
-
|
|
818
|
-
const bashSuggestions = await new BashAutocompleteProvider().getSuggestions(["git st"], 0, 6, { signal });
|
|
819
|
-
const oneOffSuggestions = await new OneOffBashAutocompleteProvider().getSuggestions(["!git st"], 0, 7, { signal });
|
|
820
|
-
|
|
821
|
-
assert.equal(bashSuggestions, null);
|
|
822
|
-
assert.equal(oneOffSuggestions, null);
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
test("mode-aware autocomplete provider preserves default results", async () => {
|
|
826
|
-
const signal = new AbortController().signal;
|
|
827
|
-
const result = {
|
|
828
|
-
items: [{ value: "status", label: "status" }],
|
|
829
|
-
prefix: "st",
|
|
830
|
-
};
|
|
831
|
-
const provider = new ModeAwareAutocompleteProvider(
|
|
832
|
-
{
|
|
833
|
-
async getSuggestions() {
|
|
834
|
-
return result;
|
|
835
|
-
},
|
|
836
|
-
applyCompletion(lines: string[], cursorLine: number, cursorCol: number) {
|
|
837
|
-
return { lines, cursorLine, cursorCol };
|
|
838
|
-
},
|
|
839
|
-
},
|
|
840
|
-
new BashAutocompleteProvider(),
|
|
841
|
-
new OneOffBashAutocompleteProvider(),
|
|
842
|
-
() => false,
|
|
843
|
-
);
|
|
844
|
-
|
|
845
|
-
const suggestions = await provider.getSuggestions(["st"], 0, 2, { signal });
|
|
846
|
-
|
|
847
|
-
assert.equal(suggestions, result);
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
test("one-off bash autocomplete provider stays inactive before the bang command starts", async () => {
|
|
851
|
-
const provider = new OneOffBashAutocompleteProvider();
|
|
852
|
-
|
|
853
|
-
assert.equal(provider.shouldTriggerFileCompletion(["!git status"], 0, 0), false);
|
|
854
|
-
assert.equal(
|
|
855
|
-
await provider.getSuggestions(["!git status"], 0, 0, { signal: new AbortController().signal }),
|
|
856
|
-
null,
|
|
857
|
-
);
|
|
858
|
-
});
|
|
859
|
-
|
|
860
|
-
test("bash editor refreshGhostSuggestion reuses the ghost scheduling path", async () => {
|
|
861
|
-
const links = ensureEditorModuleLinks();
|
|
862
|
-
|
|
863
|
-
try {
|
|
864
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
865
|
-
let scheduled = false;
|
|
866
|
-
|
|
867
|
-
getMethod(BashModeEditor.prototype, "refreshGhostSuggestion").call({
|
|
868
|
-
areCompletionsEnabled() {
|
|
869
|
-
return true;
|
|
870
|
-
},
|
|
871
|
-
scheduleGhostUpdate() {
|
|
872
|
-
scheduled = true;
|
|
873
|
-
},
|
|
874
|
-
});
|
|
875
|
-
|
|
876
|
-
assert.equal(scheduled, true);
|
|
877
|
-
} finally {
|
|
878
|
-
links.cleanup();
|
|
879
|
-
}
|
|
880
|
-
});
|
|
881
|
-
|
|
882
|
-
test("bash editor refreshGhostSuggestion clears ghosts when completions are disabled", async () => {
|
|
883
|
-
const links = ensureEditorModuleLinks();
|
|
884
|
-
|
|
885
|
-
try {
|
|
886
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
887
|
-
let cleared = false;
|
|
888
|
-
let scheduled = false;
|
|
889
|
-
|
|
890
|
-
getMethod(BashModeEditor.prototype, "refreshGhostSuggestion").call({
|
|
891
|
-
areCompletionsEnabled() {
|
|
892
|
-
return false;
|
|
893
|
-
},
|
|
894
|
-
clearGhostSuggestion() {
|
|
895
|
-
cleared = true;
|
|
896
|
-
},
|
|
897
|
-
scheduleGhostUpdate() {
|
|
898
|
-
scheduled = true;
|
|
899
|
-
},
|
|
900
|
-
});
|
|
901
|
-
|
|
902
|
-
assert.equal(cleared, true);
|
|
903
|
-
assert.equal(scheduled, false);
|
|
904
|
-
} finally {
|
|
905
|
-
links.cleanup();
|
|
906
|
-
}
|
|
907
|
-
});
|
|
908
|
-
|
|
909
|
-
test("bash editor hot path avoids full expansion and coalesces ghost work", async () => {
|
|
910
|
-
const links = ensureEditorModuleLinks();
|
|
911
|
-
|
|
912
|
-
try {
|
|
913
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
914
|
-
const { KeybindingsManager } = await import(new URL("../node_modules/@earendil-works/pi-coding-agent/dist/core/keybindings.js", import.meta.url).href);
|
|
915
|
-
const keybindings = KeybindingsManager.create();
|
|
916
|
-
const resolved: string[] = [];
|
|
917
|
-
let bashModeActive = false;
|
|
918
|
-
const editor = new BashModeEditor(
|
|
919
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
920
|
-
{ borderColor: (text: string) => text },
|
|
921
|
-
keybindings,
|
|
922
|
-
{
|
|
923
|
-
keybindings,
|
|
924
|
-
isBashModeActive: () => bashModeActive,
|
|
925
|
-
isShellRunning: () => false,
|
|
926
|
-
onExitBashMode() {},
|
|
927
|
-
onSubmitCommand() {},
|
|
928
|
-
onInterrupt() {},
|
|
929
|
-
onNotify() {},
|
|
930
|
-
getHistoryEntries: () => [],
|
|
931
|
-
resolveGhostSuggestion: async (text) => {
|
|
932
|
-
resolved.push(text);
|
|
933
|
-
return null;
|
|
934
|
-
},
|
|
935
|
-
},
|
|
936
|
-
);
|
|
937
|
-
|
|
938
|
-
(editor as { getExpandedText(): string }).getExpandedText = () => {
|
|
939
|
-
throw new Error("expanded text should not be read while typing");
|
|
940
|
-
};
|
|
941
|
-
const insertCharacter = Reflect.get(editor, "insertCharacter").bind(editor);
|
|
942
|
-
let fastInserts = 0;
|
|
943
|
-
Reflect.set(editor, "insertCharacter", (character: string) => {
|
|
944
|
-
fastInserts += 1;
|
|
945
|
-
insertCharacter(character);
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
for (const character of ["a", "A", ".", "漢", "🙂"]) editor.handleInput(character);
|
|
949
|
-
assert.equal(editor.getText(), "aA.漢🙂");
|
|
950
|
-
assert.equal(fastInserts, 5);
|
|
951
|
-
editor.onExtensionShortcut = (data) => data === "b";
|
|
952
|
-
editor.handleInput("b");
|
|
953
|
-
assert.equal(editor.getText(), "aA.漢🙂");
|
|
954
|
-
assert.equal(fastInserts, 5);
|
|
955
|
-
editor.onExtensionShortcut = undefined;
|
|
956
|
-
editor.render(80);
|
|
957
|
-
|
|
958
|
-
bashModeActive = true;
|
|
959
|
-
editor.setText("");
|
|
960
|
-
editor.handleInput("g");
|
|
961
|
-
editor.handleInput("i");
|
|
962
|
-
editor.handleInput("t");
|
|
963
|
-
assert.deepEqual(resolved, []);
|
|
964
|
-
await new Promise((resolve) => setTimeout(resolve, 75));
|
|
965
|
-
assert.deepEqual(resolved, ["git"]);
|
|
966
|
-
editor.clearGhostSuggestion();
|
|
967
|
-
|
|
968
|
-
} finally {
|
|
969
|
-
links.cleanup();
|
|
970
|
-
}
|
|
971
|
-
});
|
|
972
|
-
|
|
973
|
-
test("bash editor long ASCII backspace keeps undo without scanning the full line", async () => {
|
|
974
|
-
const links = ensureEditorModuleLinks();
|
|
975
|
-
|
|
976
|
-
try {
|
|
977
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
978
|
-
const { KeybindingsManager } = await import(new URL("../node_modules/@earendil-works/pi-coding-agent/dist/core/keybindings.js", import.meta.url).href);
|
|
979
|
-
const keybindings = KeybindingsManager.create();
|
|
980
|
-
const editor = new BashModeEditor(
|
|
981
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
982
|
-
{ borderColor: (text: string) => text },
|
|
983
|
-
keybindings,
|
|
984
|
-
{
|
|
985
|
-
keybindings,
|
|
986
|
-
isBashModeActive: () => false,
|
|
987
|
-
isShellRunning: () => false,
|
|
988
|
-
onExitBashMode() {},
|
|
989
|
-
onSubmitCommand() {},
|
|
990
|
-
onInterrupt() {},
|
|
991
|
-
onNotify() {},
|
|
992
|
-
getHistoryEntries: () => [],
|
|
993
|
-
resolveGhostSuggestion: async () => null,
|
|
994
|
-
},
|
|
995
|
-
);
|
|
996
|
-
const original = "x".repeat(5000);
|
|
997
|
-
editor.setText(original);
|
|
998
|
-
Reflect.get(editor, "undoStack").clear();
|
|
999
|
-
const segment = Reflect.get(editor, "segment").bind(editor);
|
|
1000
|
-
Reflect.set(editor, "segment", () => {
|
|
1001
|
-
throw new Error("long ASCII backspace must not segment the full line");
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
editor.handleInput("\x7f");
|
|
1005
|
-
assert.equal(editor.getText(), original.slice(0, -1));
|
|
1006
|
-
|
|
1007
|
-
editor.handleInput("\x1b[122;9u");
|
|
1008
|
-
assert.equal(editor.getText(), original);
|
|
1009
|
-
|
|
1010
|
-
Reflect.set(editor, "segment", segment);
|
|
1011
|
-
editor.setText(`${"x".repeat(4998)}\u0600a`);
|
|
1012
|
-
editor.handleInput("\x7f");
|
|
1013
|
-
assert.equal(editor.getText(), "x".repeat(4998));
|
|
1014
|
-
} finally {
|
|
1015
|
-
links.cleanup();
|
|
1016
|
-
}
|
|
1017
|
-
});
|
|
1018
|
-
|
|
1019
|
-
test("bash editor long ASCII backspace preserves custom app bindings by input sequence", async () => {
|
|
1020
|
-
const links = ensureEditorModuleLinks();
|
|
1021
|
-
|
|
1022
|
-
try {
|
|
1023
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1024
|
-
const { KeybindingsManager } = await import(new URL("../node_modules/@earendil-works/pi-coding-agent/dist/core/keybindings.js", import.meta.url).href);
|
|
1025
|
-
const keybindings = new KeybindingsManager({ "app.clear": "ctrl+h" });
|
|
1026
|
-
const editor = new BashModeEditor(
|
|
1027
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
1028
|
-
{ borderColor: (text: string) => text },
|
|
1029
|
-
keybindings,
|
|
1030
|
-
{
|
|
1031
|
-
keybindings,
|
|
1032
|
-
isBashModeActive: () => false,
|
|
1033
|
-
isShellRunning: () => false,
|
|
1034
|
-
onExitBashMode() {},
|
|
1035
|
-
onSubmitCommand() {},
|
|
1036
|
-
onInterrupt() {},
|
|
1037
|
-
onNotify() {},
|
|
1038
|
-
getHistoryEntries: () => [],
|
|
1039
|
-
resolveGhostSuggestion: async () => null,
|
|
1040
|
-
},
|
|
1041
|
-
);
|
|
1042
|
-
let cleared = false;
|
|
1043
|
-
editor.onAction("app.clear", () => {
|
|
1044
|
-
cleared = true;
|
|
1045
|
-
});
|
|
1046
|
-
const original = "x".repeat(5000);
|
|
1047
|
-
editor.setText(original);
|
|
1048
|
-
|
|
1049
|
-
editor.handleInput("\x7f");
|
|
1050
|
-
assert.equal(editor.getText(), original.slice(0, -1));
|
|
1051
|
-
assert.equal(cleared, false);
|
|
1052
|
-
|
|
1053
|
-
editor.handleInput("\x08");
|
|
1054
|
-
assert.equal(cleared, true);
|
|
1055
|
-
assert.equal(editor.getText(), original.slice(0, -1));
|
|
1056
|
-
} finally {
|
|
1057
|
-
links.cleanup();
|
|
1058
|
-
}
|
|
1059
|
-
});
|
|
1060
|
-
|
|
1061
|
-
test("bash editor fast path preserves plain custom keybindings", async () => {
|
|
1062
|
-
const links = ensureEditorModuleLinks();
|
|
1063
|
-
|
|
1064
|
-
try {
|
|
1065
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1066
|
-
const { KeybindingsManager } = await import(new URL("../node_modules/@earendil-works/pi-coding-agent/dist/core/keybindings.js", import.meta.url).href);
|
|
1067
|
-
const { getKeybindings, setKeybindings } = await import(new URL("../node_modules/@earendil-works/pi-tui/dist/index.js", import.meta.url).href);
|
|
1068
|
-
const previousKeybindings = getKeybindings();
|
|
1069
|
-
const keybindings = new KeybindingsManager({ "tui.editor.cursorLeft": "a" });
|
|
1070
|
-
|
|
1071
|
-
try {
|
|
1072
|
-
setKeybindings(keybindings);
|
|
1073
|
-
const editor = new BashModeEditor(
|
|
1074
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
1075
|
-
{ borderColor: (text: string) => text },
|
|
1076
|
-
keybindings,
|
|
1077
|
-
{
|
|
1078
|
-
keybindings,
|
|
1079
|
-
isBashModeActive: () => false,
|
|
1080
|
-
isShellRunning: () => false,
|
|
1081
|
-
onExitBashMode() {},
|
|
1082
|
-
onSubmitCommand() {},
|
|
1083
|
-
onInterrupt() {},
|
|
1084
|
-
onNotify() {},
|
|
1085
|
-
getHistoryEntries: () => [],
|
|
1086
|
-
resolveGhostSuggestion: async () => null,
|
|
1087
|
-
},
|
|
1088
|
-
);
|
|
1089
|
-
|
|
1090
|
-
editor.setText("x");
|
|
1091
|
-
editor.handleInput("a");
|
|
1092
|
-
assert.equal(editor.getText(), "x");
|
|
1093
|
-
assert.deepEqual(editor.getCursor(), { line: 0, col: 0 });
|
|
1094
|
-
|
|
1095
|
-
keybindings.setUserBindings({ "tui.editor.cursorLeft": "shift+a" });
|
|
1096
|
-
Reflect.set(editor, "plainBoundInputs", null);
|
|
1097
|
-
editor.handleInput("A");
|
|
1098
|
-
assert.equal(editor.getText(), "x");
|
|
1099
|
-
assert.deepEqual(editor.getCursor(), { line: 0, col: 0 });
|
|
1100
|
-
} finally {
|
|
1101
|
-
setKeybindings(previousKeybindings);
|
|
1102
|
-
}
|
|
1103
|
-
} finally {
|
|
1104
|
-
links.cleanup();
|
|
1105
|
-
}
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
test("bash editor dismiss clears autocomplete when mode turns off", async () => {
|
|
1109
|
-
const links = ensureEditorModuleLinks();
|
|
1110
|
-
|
|
1111
|
-
try {
|
|
1112
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1113
|
-
let aborted = false;
|
|
1114
|
-
let cancelled = false;
|
|
1115
|
-
let rendered = false;
|
|
1116
|
-
const fakeAbort = { abort() { aborted = true; } };
|
|
1117
|
-
const fakeEditor = {
|
|
1118
|
-
historyIndex: 7,
|
|
1119
|
-
shellHistoryIndex: 2,
|
|
1120
|
-
shellHistoryItems: ["git status"],
|
|
1121
|
-
shellHistoryDraft: "git st",
|
|
1122
|
-
ghostAbort: fakeAbort,
|
|
1123
|
-
ghost: { value: "git status", source: "project-history" },
|
|
1124
|
-
clearGhostSuggestion() {
|
|
1125
|
-
this.ghostAbort?.abort();
|
|
1126
|
-
this.ghostAbort = null;
|
|
1127
|
-
this.ghost = null;
|
|
1128
|
-
},
|
|
1129
|
-
cancelAutocomplete() {
|
|
1130
|
-
cancelled = true;
|
|
1131
|
-
},
|
|
1132
|
-
tui: {
|
|
1133
|
-
requestRender() {
|
|
1134
|
-
rendered = true;
|
|
1135
|
-
},
|
|
1136
|
-
},
|
|
1137
|
-
};
|
|
1138
|
-
|
|
1139
|
-
getMethod(BashModeEditor.prototype, "dismissBashModeUi").call(fakeEditor);
|
|
1140
|
-
|
|
1141
|
-
assert.equal(aborted, true);
|
|
1142
|
-
assert.equal(cancelled, true);
|
|
1143
|
-
assert.equal(rendered, true);
|
|
1144
|
-
assert.equal(fakeEditor.historyIndex, 7);
|
|
1145
|
-
assert.equal(fakeEditor.shellHistoryIndex, -1);
|
|
1146
|
-
} finally {
|
|
1147
|
-
links.cleanup();
|
|
1148
|
-
}
|
|
1149
|
-
});
|
|
1150
|
-
|
|
1151
|
-
test("bash editor shell history state does not clobber the base prompt history index", async () => {
|
|
1152
|
-
const links = ensureEditorModuleLinks();
|
|
1153
|
-
|
|
1154
|
-
try {
|
|
1155
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1156
|
-
const fakeEditor = {
|
|
1157
|
-
historyIndex: 5,
|
|
1158
|
-
shellHistoryIndex: -1,
|
|
1159
|
-
shellHistoryItems: [],
|
|
1160
|
-
shellHistoryDraft: "",
|
|
1161
|
-
ghostAbort: null,
|
|
1162
|
-
ghost: null,
|
|
1163
|
-
optionsRef: {
|
|
1164
|
-
getHistoryEntries: () => ["git stash", "git status"],
|
|
1165
|
-
onNotify: () => {},
|
|
1166
|
-
},
|
|
1167
|
-
getExpandedText() {
|
|
1168
|
-
return "git st";
|
|
1169
|
-
},
|
|
1170
|
-
setText() {},
|
|
1171
|
-
clearGhostSuggestion() {},
|
|
1172
|
-
scheduleGhostUpdate() {},
|
|
1173
|
-
};
|
|
1174
|
-
|
|
1175
|
-
getMethod(BashModeEditor.prototype, "navigateShellHistory").call(fakeEditor, -1);
|
|
1176
|
-
|
|
1177
|
-
assert.equal(fakeEditor.historyIndex, 5);
|
|
1178
|
-
assert.equal(fakeEditor.shellHistoryIndex, 0);
|
|
1179
|
-
} finally {
|
|
1180
|
-
links.cleanup();
|
|
1181
|
-
}
|
|
1182
|
-
});
|
|
1183
|
-
|
|
1184
|
-
test("bash editor recalls prompt history from single-line end without losing the live draft", async () => {
|
|
1185
|
-
const links = ensureEditorModuleLinks();
|
|
1186
|
-
|
|
1187
|
-
try {
|
|
1188
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1189
|
-
const { KeybindingsManager } = await import(new URL("../node_modules/@selesai/code/dist/core/keybindings.js", import.meta.url).href);
|
|
1190
|
-
const keybindings = KeybindingsManager.create();
|
|
1191
|
-
const createEditor = () => new BashModeEditor(
|
|
1192
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
1193
|
-
{},
|
|
1194
|
-
keybindings,
|
|
1195
|
-
{
|
|
1196
|
-
keybindings,
|
|
1197
|
-
isBashModeActive: () => false,
|
|
1198
|
-
isShellRunning: () => false,
|
|
1199
|
-
onExitBashMode() {},
|
|
1200
|
-
onSubmitCommand() {},
|
|
1201
|
-
onInterrupt() {},
|
|
1202
|
-
onNotify() {},
|
|
1203
|
-
getHistoryEntries: () => [],
|
|
1204
|
-
resolveGhostSuggestion: async () => null,
|
|
1205
|
-
},
|
|
1206
|
-
);
|
|
1207
|
-
|
|
1208
|
-
const editor = createEditor();
|
|
1209
|
-
editor.addToHistory("older prompt");
|
|
1210
|
-
editor.addToHistory("previous prompt");
|
|
1211
|
-
editor.setText("draft");
|
|
1212
|
-
|
|
1213
|
-
editor.handleInput("\x1b[A");
|
|
1214
|
-
assert.equal(editor.getText(), "previous prompt");
|
|
1215
|
-
|
|
1216
|
-
editor.handleInput("\x1b[A");
|
|
1217
|
-
assert.equal(editor.getText(), "older prompt");
|
|
1218
|
-
|
|
1219
|
-
editor.handleInput("\x1b[B");
|
|
1220
|
-
assert.equal(editor.getText(), "previous prompt");
|
|
1221
|
-
|
|
1222
|
-
editor.handleInput("\x1b[B");
|
|
1223
|
-
assert.equal(editor.getText(), "draft");
|
|
1224
|
-
|
|
1225
|
-
const midLineEditor = createEditor();
|
|
1226
|
-
midLineEditor.addToHistory("previous prompt");
|
|
1227
|
-
midLineEditor.setText("draft");
|
|
1228
|
-
midLineEditor.handleInput("\x1b[D");
|
|
1229
|
-
midLineEditor.handleInput("\x1b[A");
|
|
1230
|
-
|
|
1231
|
-
assert.equal(midLineEditor.getText(), "draft");
|
|
1232
|
-
|
|
1233
|
-
const multilineEditor = createEditor();
|
|
1234
|
-
multilineEditor.addToHistory("previous prompt");
|
|
1235
|
-
multilineEditor.setText("first line\nsecond line");
|
|
1236
|
-
multilineEditor.handleInput("\x1b[A");
|
|
1237
|
-
|
|
1238
|
-
assert.equal(multilineEditor.getText(), "first line\nsecond line");
|
|
1239
|
-
assert.equal(Reflect.get(multilineEditor, "historyIndex"), -1);
|
|
1240
|
-
|
|
1241
|
-
const firstLineEditor = createEditor();
|
|
1242
|
-
firstLineEditor.addToHistory("previous prompt");
|
|
1243
|
-
firstLineEditor.setText("first line\nsecond line");
|
|
1244
|
-
Reflect.set(Reflect.get(firstLineEditor, "state"), "cursorLine", 0);
|
|
1245
|
-
Reflect.set(Reflect.get(firstLineEditor, "state"), "cursorCol", 0);
|
|
1246
|
-
firstLineEditor.handleInput("\x1b[A");
|
|
1247
|
-
|
|
1248
|
-
assert.equal(firstLineEditor.getText(), "previous prompt");
|
|
1249
|
-
|
|
1250
|
-
const customKeybindings = new KeybindingsManager({
|
|
1251
|
-
"tui.editor.cursorUp": ["up", "alt+k"],
|
|
1252
|
-
});
|
|
1253
|
-
const customBindingEditor = new BashModeEditor(
|
|
1254
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
1255
|
-
{},
|
|
1256
|
-
customKeybindings,
|
|
1257
|
-
{
|
|
1258
|
-
keybindings: customKeybindings,
|
|
1259
|
-
isBashModeActive: () => false,
|
|
1260
|
-
isShellRunning: () => false,
|
|
1261
|
-
onExitBashMode() {},
|
|
1262
|
-
onSubmitCommand() {},
|
|
1263
|
-
onInterrupt() {},
|
|
1264
|
-
onNotify() {},
|
|
1265
|
-
getHistoryEntries: () => [],
|
|
1266
|
-
resolveGhostSuggestion: async () => null,
|
|
1267
|
-
},
|
|
1268
|
-
);
|
|
1269
|
-
customBindingEditor.addToHistory("previous prompt");
|
|
1270
|
-
customBindingEditor.setText("draft");
|
|
1271
|
-
customBindingEditor.handleInput("\x1bk");
|
|
1272
|
-
|
|
1273
|
-
assert.equal(customBindingEditor.getText(), "draft");
|
|
1274
|
-
assert.equal(Reflect.get(customBindingEditor, "historyIndex"), -1);
|
|
1275
|
-
} finally {
|
|
1276
|
-
links.cleanup();
|
|
1277
|
-
}
|
|
1278
|
-
});
|
|
1279
|
-
|
|
1280
|
-
test("bash editor escape exits bash mode", async () => {
|
|
1281
|
-
const links = ensureEditorModuleLinks();
|
|
1282
|
-
|
|
1283
|
-
try {
|
|
1284
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1285
|
-
let exited = false;
|
|
1286
|
-
let interrupted = false;
|
|
1287
|
-
|
|
1288
|
-
getMethod(BashModeEditor.prototype, "handleInput").call({
|
|
1289
|
-
optionsRef: {
|
|
1290
|
-
isBashModeActive: () => true,
|
|
1291
|
-
onExitBashMode: () => {
|
|
1292
|
-
exited = true;
|
|
1293
|
-
},
|
|
1294
|
-
isShellRunning: () => false,
|
|
1295
|
-
onInterrupt: () => {
|
|
1296
|
-
interrupted = true;
|
|
1297
|
-
},
|
|
1298
|
-
},
|
|
1299
|
-
keybindingsRef: {
|
|
1300
|
-
matches(data: string, id: string) {
|
|
1301
|
-
return data === "escape" && id === "app.interrupt";
|
|
1302
|
-
},
|
|
1303
|
-
},
|
|
1304
|
-
}, "escape");
|
|
1305
|
-
|
|
1306
|
-
assert.equal(exited, true);
|
|
1307
|
-
assert.equal(interrupted, false);
|
|
1308
|
-
} finally {
|
|
1309
|
-
links.cleanup();
|
|
1310
|
-
}
|
|
1311
|
-
});
|
|
1312
|
-
|
|
1313
|
-
test("bash editor right arrow accepts an empty-prompt ghost suggestion without submitting", async () => {
|
|
1314
|
-
const links = ensureEditorModuleLinks();
|
|
1315
|
-
|
|
1316
|
-
try {
|
|
1317
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1318
|
-
let accepted = false;
|
|
1319
|
-
let submitted = false;
|
|
1320
|
-
|
|
1321
|
-
getMethod(BashModeEditor.prototype, "handleInput").call({
|
|
1322
|
-
optionsRef: {
|
|
1323
|
-
isBashModeActive: () => true,
|
|
1324
|
-
isShellRunning: () => false,
|
|
1325
|
-
onExitBashMode: () => {},
|
|
1326
|
-
onSubmitCommand: () => {
|
|
1327
|
-
submitted = true;
|
|
1328
|
-
},
|
|
1329
|
-
onInterrupt: () => {},
|
|
1330
|
-
onNotify: () => {},
|
|
1331
|
-
},
|
|
1332
|
-
keybindingsRef: {
|
|
1333
|
-
matches(data: string, id: string) {
|
|
1334
|
-
return data === "right" && id === "tui.editor.cursorRight";
|
|
1335
|
-
},
|
|
1336
|
-
},
|
|
1337
|
-
isShowingAutocomplete() {
|
|
1338
|
-
return false;
|
|
1339
|
-
},
|
|
1340
|
-
acceptGhostSuggestion() {
|
|
1341
|
-
accepted = true;
|
|
1342
|
-
return true;
|
|
1343
|
-
},
|
|
1344
|
-
}, "right");
|
|
1345
|
-
|
|
1346
|
-
assert.equal(accepted, true);
|
|
1347
|
-
assert.equal(submitted, false);
|
|
1348
|
-
} finally {
|
|
1349
|
-
links.cleanup();
|
|
1350
|
-
}
|
|
1351
|
-
});
|
|
1352
|
-
|
|
1353
|
-
test("bash editor right arrow accepts ghost text for one-off bang commands", async () => {
|
|
1354
|
-
const links = ensureEditorModuleLinks();
|
|
1355
|
-
|
|
1356
|
-
try {
|
|
1357
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1358
|
-
let accepted = false;
|
|
1359
|
-
|
|
1360
|
-
getMethod(BashModeEditor.prototype, "handleInput").call({
|
|
1361
|
-
optionsRef: {
|
|
1362
|
-
isBashModeActive: () => false,
|
|
1363
|
-
},
|
|
1364
|
-
keybindingsRef: {
|
|
1365
|
-
matches(data: string, id: string) {
|
|
1366
|
-
return data === "right" && id === "tui.editor.cursorRight";
|
|
1367
|
-
},
|
|
1368
|
-
},
|
|
1369
|
-
getExpandedText() {
|
|
1370
|
-
return "!git st";
|
|
1371
|
-
},
|
|
1372
|
-
isOneOffBashCommandContext() {
|
|
1373
|
-
return true;
|
|
1374
|
-
},
|
|
1375
|
-
acceptGhostSuggestion() {
|
|
1376
|
-
accepted = true;
|
|
1377
|
-
return true;
|
|
1378
|
-
},
|
|
1379
|
-
}, "right");
|
|
1380
|
-
|
|
1381
|
-
assert.equal(accepted, true);
|
|
1382
|
-
} finally {
|
|
1383
|
-
links.cleanup();
|
|
1384
|
-
}
|
|
1385
|
-
});
|
|
1386
|
-
|
|
1387
|
-
test("bash editor runs copied Pi app action handlers for alt-enter", async () => {
|
|
1388
|
-
const links = ensureEditorModuleLinks();
|
|
1389
|
-
|
|
1390
|
-
try {
|
|
1391
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1392
|
-
const { KeybindingsManager } = await import(new URL("../node_modules/@selesai/code/dist/core/keybindings.js", import.meta.url).href);
|
|
1393
|
-
const { setKittyProtocolActive } = await import(new URL("../node_modules/@earendil-works/pi-tui/dist/keys.js", import.meta.url).href);
|
|
1394
|
-
const keybindings = KeybindingsManager.create();
|
|
1395
|
-
const editor = new BashModeEditor(
|
|
1396
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
1397
|
-
{},
|
|
1398
|
-
keybindings,
|
|
1399
|
-
{
|
|
1400
|
-
keybindings,
|
|
1401
|
-
isBashModeActive: () => false,
|
|
1402
|
-
isShellRunning: () => false,
|
|
1403
|
-
onExitBashMode() {},
|
|
1404
|
-
onSubmitCommand() {},
|
|
1405
|
-
onInterrupt() {},
|
|
1406
|
-
onNotify() {},
|
|
1407
|
-
getHistoryEntries: () => [],
|
|
1408
|
-
resolveGhostSuggestion: async () => null,
|
|
1409
|
-
},
|
|
1410
|
-
);
|
|
1411
|
-
|
|
1412
|
-
let handled = 0;
|
|
1413
|
-
editor.actionHandlers.set("app.message.followUp", () => {
|
|
1414
|
-
handled += 1;
|
|
1415
|
-
});
|
|
1416
|
-
|
|
1417
|
-
try {
|
|
1418
|
-
setKittyProtocolActive(false);
|
|
1419
|
-
editor.handleInput("\x1b\r");
|
|
1420
|
-
assert.equal(handled, 1);
|
|
1421
|
-
|
|
1422
|
-
setKittyProtocolActive(true);
|
|
1423
|
-
editor.handleInput("\x1b[13;3u");
|
|
1424
|
-
assert.equal(handled, 2);
|
|
1425
|
-
} finally {
|
|
1426
|
-
setKittyProtocolActive(false);
|
|
1427
|
-
}
|
|
1428
|
-
} finally {
|
|
1429
|
-
links.cleanup();
|
|
1430
|
-
}
|
|
1431
|
-
});
|
|
1432
|
-
|
|
1433
|
-
test("bash editor command-z undoes deleted text for supported encodings only", async () => {
|
|
1434
|
-
const links = ensureEditorModuleLinks();
|
|
1435
|
-
|
|
1436
|
-
try {
|
|
1437
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1438
|
-
const { KeybindingsManager } = await import(new URL("../node_modules/@selesai/code/dist/core/keybindings.js", import.meta.url).href);
|
|
1439
|
-
const keybindings = KeybindingsManager.create();
|
|
1440
|
-
const createEditor = (options: {
|
|
1441
|
-
keybindings?: typeof keybindings;
|
|
1442
|
-
isBashModeActive?: () => boolean;
|
|
1443
|
-
isShellRunning?: () => boolean;
|
|
1444
|
-
onExitBashMode?: () => void;
|
|
1445
|
-
onInterrupt?: () => void;
|
|
1446
|
-
resolveGhostSuggestion?: (text: string) => Promise<null>;
|
|
1447
|
-
} = {}) => new BashModeEditor(
|
|
1448
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
1449
|
-
{},
|
|
1450
|
-
options.keybindings ?? keybindings,
|
|
1451
|
-
{
|
|
1452
|
-
keybindings: options.keybindings ?? keybindings,
|
|
1453
|
-
isBashModeActive: options.isBashModeActive ?? (() => false),
|
|
1454
|
-
isShellRunning: options.isShellRunning ?? (() => false),
|
|
1455
|
-
onExitBashMode: options.onExitBashMode ?? (() => {}),
|
|
1456
|
-
onSubmitCommand() {},
|
|
1457
|
-
onInterrupt: options.onInterrupt ?? (() => {}),
|
|
1458
|
-
onNotify() {},
|
|
1459
|
-
getHistoryEntries: () => [],
|
|
1460
|
-
resolveGhostSuggestion: options.resolveGhostSuggestion ?? (async () => null),
|
|
1461
|
-
},
|
|
1462
|
-
);
|
|
1463
|
-
|
|
1464
|
-
for (const data of ["\x1b[122;9u", "\x1b[122;9:1u", "\x1b[122;9:2u", "\x1b[27;9;122~"]) {
|
|
1465
|
-
const editor = createEditor();
|
|
1466
|
-
|
|
1467
|
-
for (const char of "hello") editor.handleInput(char);
|
|
1468
|
-
editor.handleInput("\x7f");
|
|
1469
|
-
assert.equal(editor.getText(), "hell");
|
|
1470
|
-
|
|
1471
|
-
editor.handleInput(data);
|
|
1472
|
-
assert.equal(editor.getText(), "hello");
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
const editor = createEditor();
|
|
1476
|
-
|
|
1477
|
-
for (const char of "hello") editor.handleInput(char);
|
|
1478
|
-
editor.handleInput("\x7f");
|
|
1479
|
-
editor.handleInput("\x1b[122;9u");
|
|
1480
|
-
assert.equal(editor.getText(), "hello");
|
|
1481
|
-
|
|
1482
|
-
editor.handleInput("\x1b[122;9:3u");
|
|
1483
|
-
assert.equal(editor.getText(), "hello");
|
|
1484
|
-
|
|
1485
|
-
editor.handleInput("\x7f");
|
|
1486
|
-
editor.handleInput("\x1b[27;9;90~");
|
|
1487
|
-
assert.equal(editor.getText(), "hell");
|
|
1488
|
-
|
|
1489
|
-
const plainEditor = createEditor();
|
|
1490
|
-
plainEditor.handleInput("z");
|
|
1491
|
-
assert.equal(plainEditor.getText(), "z");
|
|
1492
|
-
|
|
1493
|
-
for (const action of ["app.interrupt", "app.clear"]) {
|
|
1494
|
-
let exited = false;
|
|
1495
|
-
let interrupted = false;
|
|
1496
|
-
const customizedKeybindings = new KeybindingsManager({ [action]: "super+z" });
|
|
1497
|
-
assert.equal(customizedKeybindings.matches("\x1b[122;9u", action), true);
|
|
1498
|
-
const customizedEditor = createEditor({
|
|
1499
|
-
keybindings: customizedKeybindings,
|
|
1500
|
-
isBashModeActive: () => true,
|
|
1501
|
-
isShellRunning: () => true,
|
|
1502
|
-
onExitBashMode: () => {
|
|
1503
|
-
exited = true;
|
|
1504
|
-
},
|
|
1505
|
-
onInterrupt: () => {
|
|
1506
|
-
interrupted = true;
|
|
1507
|
-
},
|
|
1508
|
-
});
|
|
1509
|
-
|
|
1510
|
-
for (const char of "hello") customizedEditor.handleInput(char);
|
|
1511
|
-
customizedEditor.handleInput("\x7f");
|
|
1512
|
-
customizedEditor.handleInput("\x1b[122;9u");
|
|
1513
|
-
|
|
1514
|
-
assert.equal(customizedEditor.getText(), "hello");
|
|
1515
|
-
assert.equal(exited, false);
|
|
1516
|
-
assert.equal(interrupted, false);
|
|
1517
|
-
}
|
|
1518
|
-
} finally {
|
|
1519
|
-
links.cleanup();
|
|
1520
|
-
}
|
|
1521
|
-
});
|
|
1522
|
-
|
|
1523
|
-
test("bash editor command-z resets shell history and updates ghost state", async () => {
|
|
1524
|
-
const links = ensureEditorModuleLinks();
|
|
1525
|
-
|
|
1526
|
-
try {
|
|
1527
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1528
|
-
const { KeybindingsManager } = await import(new URL("../node_modules/@selesai/code/dist/core/keybindings.js", import.meta.url).href);
|
|
1529
|
-
const keybindings = KeybindingsManager.create();
|
|
1530
|
-
const createEditor = (options: {
|
|
1531
|
-
isBashModeActive?: () => boolean;
|
|
1532
|
-
resolveGhostSuggestion?: (text: string) => Promise<null>;
|
|
1533
|
-
} = {}) => new BashModeEditor(
|
|
1534
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
1535
|
-
{},
|
|
1536
|
-
keybindings,
|
|
1537
|
-
{
|
|
1538
|
-
keybindings,
|
|
1539
|
-
isBashModeActive: options.isBashModeActive ?? (() => false),
|
|
1540
|
-
isShellRunning: () => false,
|
|
1541
|
-
onExitBashMode() {},
|
|
1542
|
-
onSubmitCommand() {},
|
|
1543
|
-
onInterrupt() {},
|
|
1544
|
-
onNotify() {},
|
|
1545
|
-
getHistoryEntries: () => [],
|
|
1546
|
-
resolveGhostSuggestion: options.resolveGhostSuggestion ?? (async () => null),
|
|
1547
|
-
},
|
|
1548
|
-
);
|
|
1549
|
-
const ghostRefreshes: string[] = [];
|
|
1550
|
-
const shellEditor = createEditor({
|
|
1551
|
-
isBashModeActive: () => true,
|
|
1552
|
-
resolveGhostSuggestion: async (text) => {
|
|
1553
|
-
ghostRefreshes.push(text);
|
|
1554
|
-
return null;
|
|
1555
|
-
},
|
|
1556
|
-
});
|
|
1557
|
-
|
|
1558
|
-
shellEditor.handleInput("a");
|
|
1559
|
-
shellEditor.handleInput("\x7f");
|
|
1560
|
-
Reflect.set(shellEditor, "shellHistoryIndex", 0);
|
|
1561
|
-
Reflect.set(shellEditor, "shellHistoryItems", ["git status"]);
|
|
1562
|
-
Reflect.set(shellEditor, "shellHistoryDraft", "git");
|
|
1563
|
-
shellEditor.handleInput("\x1b[122;9u");
|
|
1564
|
-
await new Promise((resolve) => setTimeout(resolve, 75));
|
|
1565
|
-
|
|
1566
|
-
assert.equal(shellEditor.getText(), "a");
|
|
1567
|
-
assert.equal(Reflect.get(shellEditor, "shellHistoryIndex"), -1);
|
|
1568
|
-
assert.deepEqual(Reflect.get(shellEditor, "shellHistoryItems"), []);
|
|
1569
|
-
assert.equal(Reflect.get(shellEditor, "shellHistoryDraft"), "");
|
|
1570
|
-
assert.equal(ghostRefreshes.at(-1), "a");
|
|
1571
|
-
|
|
1572
|
-
const plainEditor = createEditor();
|
|
1573
|
-
plainEditor.handleInput("z");
|
|
1574
|
-
plainEditor.handleInput("\x7f");
|
|
1575
|
-
Reflect.set(plainEditor, "ghost", { value: "stale" });
|
|
1576
|
-
plainEditor.handleInput("\x1b[122;9u");
|
|
1577
|
-
assert.equal(Reflect.get(plainEditor, "ghost"), null);
|
|
1578
|
-
} finally {
|
|
1579
|
-
links.cleanup();
|
|
1580
|
-
}
|
|
1581
|
-
});
|
|
1582
|
-
|
|
1583
|
-
test("bash editor command arrows jump to editor boundaries", async () => {
|
|
1584
|
-
const links = ensureEditorModuleLinks();
|
|
1585
|
-
|
|
1586
|
-
try {
|
|
1587
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1588
|
-
const { KeybindingsManager } = await import(new URL("../node_modules/@selesai/code/dist/core/keybindings.js", import.meta.url).href);
|
|
1589
|
-
const keybindings = KeybindingsManager.create();
|
|
1590
|
-
let renderRequests = 0;
|
|
1591
|
-
const editor = new BashModeEditor(
|
|
1592
|
-
{ requestRender() { renderRequests += 1; }, terminal: { columns: 80, rows: 24 } },
|
|
1593
|
-
{},
|
|
1594
|
-
keybindings,
|
|
1595
|
-
{
|
|
1596
|
-
keybindings,
|
|
1597
|
-
isBashModeActive: () => false,
|
|
1598
|
-
isShellRunning: () => false,
|
|
1599
|
-
onExitBashMode() {},
|
|
1600
|
-
onSubmitCommand() {},
|
|
1601
|
-
onInterrupt() {},
|
|
1602
|
-
onNotify() {},
|
|
1603
|
-
getHistoryEntries: () => [],
|
|
1604
|
-
resolveGhostSuggestion: async () => null,
|
|
1605
|
-
},
|
|
1606
|
-
);
|
|
1607
|
-
|
|
1608
|
-
editor.setText("alpha\nbravo\ncharlie");
|
|
1609
|
-
assert.deepEqual(editor.getCursor(), { line: 2, col: 7 });
|
|
1610
|
-
|
|
1611
|
-
editor.handleInput("\x1b[A");
|
|
1612
|
-
assert.notDeepEqual(editor.getCursor(), { line: 0, col: 0 });
|
|
1613
|
-
editor.handleInput("\x1b[B");
|
|
1614
|
-
assert.deepEqual(editor.getCursor(), { line: 2, col: 7 });
|
|
1615
|
-
|
|
1616
|
-
editor.handleInput("\x1b[1;9A");
|
|
1617
|
-
assert.notDeepEqual(editor.getCursor(), { line: 0, col: 0 });
|
|
1618
|
-
|
|
1619
|
-
editor.handleInput("\x1b[1;10A");
|
|
1620
|
-
assert.deepEqual(editor.getCursor(), { line: 0, col: 0 });
|
|
1621
|
-
|
|
1622
|
-
editor.handleInput("\x1b[27;10;66~");
|
|
1623
|
-
assert.deepEqual(editor.getCursor(), { line: 2, col: 7 });
|
|
1624
|
-
|
|
1625
|
-
editor.handleInput("\x1b[27;10;65~");
|
|
1626
|
-
assert.deepEqual(editor.getCursor(), { line: 0, col: 0 });
|
|
1627
|
-
|
|
1628
|
-
editor.handleInput("\x1b[57420;10u");
|
|
1629
|
-
assert.deepEqual(editor.getCursor(), { line: 2, col: 7 });
|
|
1630
|
-
|
|
1631
|
-
editor.handleInput("\x1b[57423;10u");
|
|
1632
|
-
assert.deepEqual(editor.getCursor(), { line: 0, col: 0 });
|
|
1633
|
-
|
|
1634
|
-
editor.handleInput("\x1b[1;10F");
|
|
1635
|
-
assert.deepEqual(editor.getCursor(), { line: 2, col: 7 });
|
|
1636
|
-
|
|
1637
|
-
editor.handleInput("\x1b[1;10:3A");
|
|
1638
|
-
assert.deepEqual(editor.getCursor(), { line: 2, col: 7 });
|
|
1639
|
-
|
|
1640
|
-
const customEditor = new BashModeEditor(
|
|
1641
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
1642
|
-
{},
|
|
1643
|
-
keybindings,
|
|
1644
|
-
{
|
|
1645
|
-
keybindings,
|
|
1646
|
-
isBashModeActive: () => false,
|
|
1647
|
-
isShellRunning: () => false,
|
|
1648
|
-
onExitBashMode() {},
|
|
1649
|
-
onSubmitCommand() {},
|
|
1650
|
-
editorBoundaryShortcuts: { start: "ctrl+shift+u", end: "ctrl+shift+d" },
|
|
1651
|
-
onInterrupt() {},
|
|
1652
|
-
onNotify() {},
|
|
1653
|
-
getHistoryEntries: () => [],
|
|
1654
|
-
resolveGhostSuggestion: async () => null,
|
|
1655
|
-
},
|
|
1656
|
-
);
|
|
1657
|
-
customEditor.setText("alpha\nbravo\ncharlie");
|
|
1658
|
-
customEditor.handleInput("\x1b[117;6u");
|
|
1659
|
-
assert.deepEqual(customEditor.getCursor(), { line: 0, col: 0 });
|
|
1660
|
-
customEditor.handleInput("\x1b[100;6u");
|
|
1661
|
-
assert.deepEqual(customEditor.getCursor(), { line: 2, col: 7 });
|
|
1662
|
-
|
|
1663
|
-
const configuredCommandEditor = new BashModeEditor(
|
|
1664
|
-
{ requestRender() {}, terminal: { columns: 80, rows: 24 } },
|
|
1665
|
-
{},
|
|
1666
|
-
keybindings,
|
|
1667
|
-
{
|
|
1668
|
-
keybindings,
|
|
1669
|
-
isBashModeActive: () => false,
|
|
1670
|
-
isShellRunning: () => false,
|
|
1671
|
-
onExitBashMode() {},
|
|
1672
|
-
onSubmitCommand() {},
|
|
1673
|
-
editorBoundaryShortcuts: { start: "super+shift+up", end: "super+shift+down" },
|
|
1674
|
-
onInterrupt() {},
|
|
1675
|
-
onNotify() {},
|
|
1676
|
-
getHistoryEntries: () => [],
|
|
1677
|
-
resolveGhostSuggestion: async () => null,
|
|
1678
|
-
},
|
|
1679
|
-
);
|
|
1680
|
-
configuredCommandEditor.setText("alpha\nbravo\ncharlie");
|
|
1681
|
-
configuredCommandEditor.handleInput("\x1b[1;10A");
|
|
1682
|
-
assert.deepEqual(configuredCommandEditor.getCursor(), { line: 0, col: 0 });
|
|
1683
|
-
configuredCommandEditor.handleInput("\x1b[1;10B");
|
|
1684
|
-
assert.deepEqual(configuredCommandEditor.getCursor(), { line: 2, col: 7 });
|
|
1685
|
-
|
|
1686
|
-
assert.equal(renderRequests, 6);
|
|
1687
|
-
} finally {
|
|
1688
|
-
links.cleanup();
|
|
1689
|
-
}
|
|
1690
|
-
});
|
|
1691
|
-
|
|
1692
|
-
test("bash editor enter does not accept ghost text while a shell command is running", async () => {
|
|
1693
|
-
const links = ensureEditorModuleLinks();
|
|
1694
|
-
|
|
1695
|
-
try {
|
|
1696
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1697
|
-
let warned = false;
|
|
1698
|
-
let submitted = false;
|
|
1699
|
-
|
|
1700
|
-
getMethod(BashModeEditor.prototype, "handleInput").call({
|
|
1701
|
-
ghost: { value: "git status", source: "project-history" },
|
|
1702
|
-
optionsRef: {
|
|
1703
|
-
isBashModeActive: () => true,
|
|
1704
|
-
isShellRunning: () => true,
|
|
1705
|
-
onExitBashMode: () => {},
|
|
1706
|
-
onInterrupt: () => {},
|
|
1707
|
-
onSubmitCommand: () => {
|
|
1708
|
-
submitted = true;
|
|
1709
|
-
},
|
|
1710
|
-
onNotify: (message: string) => {
|
|
1711
|
-
warned = message === "Shell command already running";
|
|
1712
|
-
},
|
|
1713
|
-
},
|
|
1714
|
-
keybindingsRef: {
|
|
1715
|
-
matches(_data: string, id: string) {
|
|
1716
|
-
return id === "tui.input.submit";
|
|
1717
|
-
},
|
|
1718
|
-
},
|
|
1719
|
-
getExpandedText() {
|
|
1720
|
-
return "git st";
|
|
1721
|
-
},
|
|
1722
|
-
isShowingAutocomplete() {
|
|
1723
|
-
return false;
|
|
1724
|
-
},
|
|
1725
|
-
acceptGhostSuggestion() {
|
|
1726
|
-
throw new Error("ghost should not be accepted while running");
|
|
1727
|
-
},
|
|
1728
|
-
}, "enter");
|
|
1729
|
-
|
|
1730
|
-
assert.equal(warned, true);
|
|
1731
|
-
assert.equal(submitted, false);
|
|
1732
|
-
} finally {
|
|
1733
|
-
links.cleanup();
|
|
1734
|
-
}
|
|
1735
|
-
});
|
|
1736
|
-
|
|
1737
|
-
test("bash editor enter submits the typed command without accepting ghost text", async () => {
|
|
1738
|
-
const links = ensureEditorModuleLinks();
|
|
1739
|
-
|
|
1740
|
-
try {
|
|
1741
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1742
|
-
let submittedCommand = "";
|
|
1743
|
-
|
|
1744
|
-
getMethod(BashModeEditor.prototype, "handleInput").call({
|
|
1745
|
-
ghost: { value: "git diff --staged", source: "project-history" },
|
|
1746
|
-
optionsRef: {
|
|
1747
|
-
isBashModeActive: () => true,
|
|
1748
|
-
isShellRunning: () => false,
|
|
1749
|
-
onExitBashMode: () => {},
|
|
1750
|
-
onInterrupt: () => {},
|
|
1751
|
-
onNotify: () => {},
|
|
1752
|
-
onSubmitCommand: (command: string) => {
|
|
1753
|
-
submittedCommand = command;
|
|
1754
|
-
},
|
|
1755
|
-
},
|
|
1756
|
-
keybindingsRef: {
|
|
1757
|
-
matches(_data: string, id: string) {
|
|
1758
|
-
return id === "tui.input.submit";
|
|
1759
|
-
},
|
|
1760
|
-
},
|
|
1761
|
-
getExpandedText() {
|
|
1762
|
-
return "git diff";
|
|
1763
|
-
},
|
|
1764
|
-
acceptGhostSuggestion() {
|
|
1765
|
-
throw new Error("enter should not accept ghost text");
|
|
1766
|
-
},
|
|
1767
|
-
clearGhostSuggestion() {},
|
|
1768
|
-
setText() {},
|
|
1769
|
-
refreshGhostSuggestion() {},
|
|
1770
|
-
shellHistoryIndex: -1,
|
|
1771
|
-
shellHistoryItems: [],
|
|
1772
|
-
shellHistoryDraft: "",
|
|
1773
|
-
}, "enter");
|
|
1774
|
-
|
|
1775
|
-
assert.equal(submittedCommand, "git diff");
|
|
1776
|
-
} finally {
|
|
1777
|
-
links.cleanup();
|
|
1778
|
-
}
|
|
1779
|
-
});
|
|
1780
|
-
|
|
1781
|
-
test("one-off bang submit does not accept ghost text before submitting", async () => {
|
|
1782
|
-
const links = ensureEditorModuleLinks();
|
|
1783
|
-
|
|
1784
|
-
try {
|
|
1785
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1786
|
-
const { CustomEditor } = await import(new URL("../node_modules/@selesai/code/dist/modes/interactive/components/custom-editor.js", import.meta.url).href);
|
|
1787
|
-
|
|
1788
|
-
let delegated = 0;
|
|
1789
|
-
const superHandleInput = CustomEditor.prototype.handleInput;
|
|
1790
|
-
CustomEditor.prototype.handleInput = function handleInput() {
|
|
1791
|
-
delegated += 1;
|
|
1792
|
-
};
|
|
1793
|
-
|
|
1794
|
-
try {
|
|
1795
|
-
getMethod(BashModeEditor.prototype, "handleInput").call({
|
|
1796
|
-
ghost: { value: "!git diff --staged", source: "project-history" },
|
|
1797
|
-
optionsRef: {
|
|
1798
|
-
isBashModeActive: () => false,
|
|
1799
|
-
},
|
|
1800
|
-
keybindingsRef: {
|
|
1801
|
-
matches(_data: string, id: string) {
|
|
1802
|
-
return id === "tui.input.submit";
|
|
1803
|
-
},
|
|
1804
|
-
},
|
|
1805
|
-
getExpandedText() {
|
|
1806
|
-
return "!git diff";
|
|
1807
|
-
},
|
|
1808
|
-
isOneOffBashCommandContext() {
|
|
1809
|
-
return true;
|
|
1810
|
-
},
|
|
1811
|
-
isShellCompletionContext() {
|
|
1812
|
-
return true;
|
|
1813
|
-
},
|
|
1814
|
-
acceptGhostSuggestion() {
|
|
1815
|
-
throw new Error("enter should not accept ghost text for one-off bash commands");
|
|
1816
|
-
},
|
|
1817
|
-
}, "enter");
|
|
1818
|
-
} finally {
|
|
1819
|
-
CustomEditor.prototype.handleInput = superHandleInput;
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
assert.equal(delegated, 1);
|
|
1823
|
-
} finally {
|
|
1824
|
-
links.cleanup();
|
|
1825
|
-
}
|
|
1826
|
-
});
|
|
1827
|
-
|
|
1828
|
-
test("bash editor does not accept a hidden ghost suggestion when the cursor is not at the end", async () => {
|
|
1829
|
-
const links = ensureEditorModuleLinks();
|
|
1830
|
-
|
|
1831
|
-
try {
|
|
1832
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1833
|
-
const accepted = getMethod(BashModeEditor.prototype, "acceptGhostSuggestion").call({
|
|
1834
|
-
ghost: { value: "git status", source: "project-history" },
|
|
1835
|
-
getText() {
|
|
1836
|
-
return "git st";
|
|
1837
|
-
},
|
|
1838
|
-
getCursor() {
|
|
1839
|
-
return { line: 0, col: 3 };
|
|
1840
|
-
},
|
|
1841
|
-
setText() {
|
|
1842
|
-
throw new Error("hidden ghost should not be accepted");
|
|
1843
|
-
},
|
|
1844
|
-
clearGhostSuggestion() {},
|
|
1845
|
-
});
|
|
1846
|
-
|
|
1847
|
-
assert.equal(accepted, false);
|
|
1848
|
-
} finally {
|
|
1849
|
-
links.cleanup();
|
|
1850
|
-
}
|
|
1851
|
-
});
|
|
1852
|
-
|
|
1853
|
-
test("bash editor submit clears the prompt and refreshes the empty ghost suggestion", async () => {
|
|
1854
|
-
const links = ensureEditorModuleLinks();
|
|
1855
|
-
|
|
1856
|
-
try {
|
|
1857
|
-
const { BashModeEditor } = await import("../bash-mode/editor.ts");
|
|
1858
|
-
let submitted = false;
|
|
1859
|
-
let cleared = false;
|
|
1860
|
-
let refreshed = false;
|
|
1861
|
-
|
|
1862
|
-
getMethod(BashModeEditor.prototype, "handleInput").call({
|
|
1863
|
-
optionsRef: {
|
|
1864
|
-
isBashModeActive: () => true,
|
|
1865
|
-
isShellRunning: () => false,
|
|
1866
|
-
onExitBashMode: () => {},
|
|
1867
|
-
onInterrupt: () => {},
|
|
1868
|
-
onNotify: () => {},
|
|
1869
|
-
onSubmitCommand: (command: string) => {
|
|
1870
|
-
submitted = command === "git status";
|
|
1871
|
-
},
|
|
1872
|
-
},
|
|
1873
|
-
keybindingsRef: {
|
|
1874
|
-
matches(_data: string, id: string) {
|
|
1875
|
-
return id === "tui.input.submit";
|
|
1876
|
-
},
|
|
1877
|
-
},
|
|
1878
|
-
isShowingAutocomplete() {
|
|
1879
|
-
return false;
|
|
1880
|
-
},
|
|
1881
|
-
acceptGhostSuggestion() {
|
|
1882
|
-
return false;
|
|
1883
|
-
},
|
|
1884
|
-
getExpandedText() {
|
|
1885
|
-
return "git status";
|
|
1886
|
-
},
|
|
1887
|
-
clearGhostSuggestion() {},
|
|
1888
|
-
setText(value: string) {
|
|
1889
|
-
cleared = value === "";
|
|
1890
|
-
},
|
|
1891
|
-
refreshGhostSuggestion() {
|
|
1892
|
-
refreshed = true;
|
|
1893
|
-
},
|
|
1894
|
-
shellHistoryIndex: 3,
|
|
1895
|
-
shellHistoryItems: ["git status"],
|
|
1896
|
-
shellHistoryDraft: "git st",
|
|
1897
|
-
}, "enter");
|
|
1898
|
-
|
|
1899
|
-
assert.equal(submitted, true);
|
|
1900
|
-
assert.equal(cleared, true);
|
|
1901
|
-
assert.equal(refreshed, true);
|
|
1902
|
-
} finally {
|
|
1903
|
-
links.cleanup();
|
|
1904
|
-
}
|
|
1905
|
-
});
|