pi-double-esc 1.0.13 → 1.0.15
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/README.md +6 -0
- package/double-esc.ts +37 -4
- package/package.json +7 -3
- package/vitest.config.ts +4 -0
package/README.md
CHANGED
|
@@ -101,3 +101,9 @@ npm run lint:dead # check for unused exports
|
|
|
101
101
|
## License
|
|
102
102
|
|
|
103
103
|
MIT
|
|
104
|
+
|
|
105
|
+
## Pi 0.99 compatibility (1.0.15)
|
|
106
|
+
|
|
107
|
+
Preserves single-Escape autocomplete dismissal, restores editor methods/factories and clears debounce timers on shutdown. Non-TUI sessions are untouched; cached render output is not mutated.
|
|
108
|
+
|
|
109
|
+
Tested with Pi 0.99.0. Host-provided Pi packages are wildcard peers, not bundled dependencies; development uses exact 0.99.0 versions.
|
package/double-esc.ts
CHANGED
|
@@ -46,6 +46,7 @@ function decorateDoubleEscapeEditor(
|
|
|
46
46
|
tui: TUI,
|
|
47
47
|
appTheme: Theme,
|
|
48
48
|
isIdle: () => boolean,
|
|
49
|
+
onCleanup: (cleanup: () => void) => void,
|
|
49
50
|
): EditorComponent {
|
|
50
51
|
let escapeState: DoubleEscapeState = createInitialState();
|
|
51
52
|
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -55,8 +56,18 @@ function decorateDoubleEscapeEditor(
|
|
|
55
56
|
debounceTimer = undefined;
|
|
56
57
|
};
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
editor.handleInput
|
|
59
|
+
let active = true;
|
|
60
|
+
const originalHandleInput = editor.handleInput;
|
|
61
|
+
const originalRender = editor.render;
|
|
62
|
+
const handleInput = originalHandleInput.bind(editor);
|
|
63
|
+
const wrappedInput = (data: string): void => {
|
|
64
|
+
const autocomplete = editor as EditorComponent & { isShowingAutocomplete?: () => boolean };
|
|
65
|
+
if (!active || autocomplete.isShowingAutocomplete?.()) {
|
|
66
|
+
clearDebounce();
|
|
67
|
+
escapeState = createInitialState();
|
|
68
|
+
handleInput(data);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
60
71
|
if (!matchesKey(data, "escape")) {
|
|
61
72
|
if (escapeState.hintActive) {
|
|
62
73
|
escapeState = handleOtherKey(escapeState).state;
|
|
@@ -86,7 +97,7 @@ function decorateDoubleEscapeEditor(
|
|
|
86
97
|
|
|
87
98
|
const render = editor.render.bind(editor);
|
|
88
99
|
editor.render = (width: number): string[] => {
|
|
89
|
-
const lines = render(width);
|
|
100
|
+
const lines = [...render(width)];
|
|
90
101
|
if (!escapeState.hintActive || lines.length === 0) return lines;
|
|
91
102
|
|
|
92
103
|
const label = " esc again to abort ";
|
|
@@ -104,6 +115,15 @@ function decorateDoubleEscapeEditor(
|
|
|
104
115
|
return lines;
|
|
105
116
|
};
|
|
106
117
|
|
|
118
|
+
editor.handleInput = wrappedInput;
|
|
119
|
+
const wrappedRender = editor.render;
|
|
120
|
+
onCleanup(() => {
|
|
121
|
+
active = false;
|
|
122
|
+
clearDebounce();
|
|
123
|
+
escapeState = createInitialState();
|
|
124
|
+
if (editor.handleInput === wrappedInput) editor.handleInput = originalHandleInput;
|
|
125
|
+
if (editor.render === wrappedRender) editor.render = originalRender;
|
|
126
|
+
});
|
|
107
127
|
return editor;
|
|
108
128
|
}
|
|
109
129
|
|
|
@@ -117,7 +137,11 @@ type EditorFactory = NonNullable<ReturnType<ExtensionContext["ui"]["getEditorCom
|
|
|
117
137
|
type DecoratedEditorFactory = EditorFactory & { [DECORATED_FACTORY]?: true };
|
|
118
138
|
|
|
119
139
|
export default function (pi: ExtensionAPI) {
|
|
140
|
+
const cleanups = new Set<() => void>();
|
|
141
|
+
let installed: DecoratedEditorFactory | undefined;
|
|
142
|
+
let previous: EditorFactory | undefined;
|
|
120
143
|
pi.on("session_start", (_event, ctx) => {
|
|
144
|
+
if (ctx.mode !== "tui") return;
|
|
121
145
|
const previousEditorFactory = ctx.ui.getEditorComponent() as DecoratedEditorFactory | undefined;
|
|
122
146
|
if (previousEditorFactory?.[DECORATED_FACTORY]) return;
|
|
123
147
|
|
|
@@ -125,10 +149,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
125
149
|
const editor =
|
|
126
150
|
previousEditorFactory?.(tui, editorTheme, keybindings) ??
|
|
127
151
|
new CustomEditor(tui, editorTheme, keybindings);
|
|
128
|
-
return decorateDoubleEscapeEditor(editor, tui, ctx.ui.theme, () => ctx.isIdle());
|
|
152
|
+
return decorateDoubleEscapeEditor(editor, tui, ctx.ui.theme, () => ctx.isIdle(), (cleanup) => cleanups.add(cleanup));
|
|
129
153
|
};
|
|
130
154
|
editorFactory[DECORATED_FACTORY] = true;
|
|
131
155
|
|
|
156
|
+
previous = previousEditorFactory;
|
|
157
|
+
installed = editorFactory;
|
|
132
158
|
ctx.ui.setEditorComponent(editorFactory);
|
|
133
159
|
});
|
|
160
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
161
|
+
for (const cleanup of cleanups) cleanup();
|
|
162
|
+
cleanups.clear();
|
|
163
|
+
if (installed && ctx.ui.getEditorComponent() === installed) ctx.ui.setEditorComponent(previous);
|
|
164
|
+
installed = undefined;
|
|
165
|
+
previous = undefined;
|
|
166
|
+
});
|
|
134
167
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-double-esc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "Prevent accidental Escape from aborting the LLM — requires double-press to interrupt while streaming",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Tom X Nguyen",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"lint:dead": "knip --no-gitignore"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
41
|
-
"@earendil-works/pi-tui": "0.
|
|
40
|
+
"@earendil-works/pi-coding-agent": "0.99.0",
|
|
41
|
+
"@earendil-works/pi-tui": "0.99.0",
|
|
42
42
|
"@types/node": "25.9.1",
|
|
43
43
|
"@vitest/coverage-v8": "4.1.7",
|
|
44
44
|
"knip": "6.14.1",
|
|
@@ -52,5 +52,9 @@
|
|
|
52
52
|
},
|
|
53
53
|
"overrides": {
|
|
54
54
|
"brace-expansion": "5.0.6"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
58
|
+
"@earendil-works/pi-tui": "*"
|
|
55
59
|
}
|
|
56
60
|
}
|
package/vitest.config.ts
CHANGED
|
@@ -4,6 +4,10 @@ export default defineConfig({
|
|
|
4
4
|
test: {
|
|
5
5
|
globals: true,
|
|
6
6
|
environment: "node",
|
|
7
|
+
// pi 0.87's module graph is much heavier to first-evaluate; each test
|
|
8
|
+
// file's first test pays the cold vi.resetModules() import, which can
|
|
9
|
+
// exceed the 5s default under load. Give the harness headroom.
|
|
10
|
+
testTimeout: 20_000,
|
|
7
11
|
include: ["__tests__/**/*.test.ts"],
|
|
8
12
|
exclude: ["node_modules", "dist", ".idea", ".git", ".cache"],
|
|
9
13
|
coverage: {
|