pi-cc-extensions 0.8.45 → 0.8.47
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.en.md +11 -26
- package/README.md +40 -23
- package/assets/readme/welcome.webp +0 -0
- package/extensions/claude-code-style.ts +554 -519
- package/extensions/context.ts +196 -106
- package/extensions/index.ts +2 -2
- package/extensions/markdown-enhance.ts +208 -0
- package/extensions/pi-aliases.ts +0 -3
- package/extensions/tool-diff/diff-renderer.ts +9 -4
- package/extensions/tool-grouping.ts +2 -1
- package/package.json +10 -14
- package/themes/cc-dark.json +1 -7
- package/themes/cc-light.json +91 -0
- package/extensions/compact-style.ts +0 -1268
- package/extensions/fixed-editor.ts +0 -458
- package/extensions/subagent-notification.ts +0 -121
- package/node_modules/@tifan/pi-fixed-editor/README.md +0 -36
- package/node_modules/@tifan/pi-fixed-editor/package.json +0 -46
- package/node_modules/@tifan/pi-fixed-editor/src/cluster.ts +0 -113
- package/node_modules/@tifan/pi-fixed-editor/src/index.ts +0 -259
- package/node_modules/@tifan/pi-fixed-editor/src/terminal-split.ts +0 -1337
- package/themes/github-dark-default.json +0 -91
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CURSOR_MARKER,
|
|
3
|
-
truncateToWidth,
|
|
4
|
-
visibleWidth,
|
|
5
|
-
} from "@earendil-works/pi-tui"
|
|
6
|
-
|
|
7
|
-
export interface FixedEditorClusterInput {
|
|
8
|
-
width: number
|
|
9
|
-
terminalRows: number
|
|
10
|
-
statusLines?: string[]
|
|
11
|
-
aboveWidgetLines?: string[]
|
|
12
|
-
editorLines: string[]
|
|
13
|
-
belowWidgetLines?: string[]
|
|
14
|
-
footerLines?: string[]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface FixedEditorCursor {
|
|
18
|
-
row: number
|
|
19
|
-
col: number
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface FixedEditorClusterRender {
|
|
23
|
-
lines: string[]
|
|
24
|
-
cursor: FixedEditorCursor | null
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function normalizeLines(lines: string[] | undefined, width: number): string[] {
|
|
28
|
-
if (!lines || width <= 0) return []
|
|
29
|
-
|
|
30
|
-
return lines
|
|
31
|
-
.filter((line) => line !== undefined && line !== null)
|
|
32
|
-
.map((line) =>
|
|
33
|
-
visibleWidth(line) > width
|
|
34
|
-
? truncateToWidth(line, width, "", true)
|
|
35
|
-
: line,
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function takeTail(lines: string[], count: number): string[] {
|
|
40
|
-
if (count <= 0) return []
|
|
41
|
-
return lines.length <= count ? lines : lines.slice(lines.length - count)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function capEditorLines(lines: string[], count: number): string[] {
|
|
45
|
-
if (count <= 0) return []
|
|
46
|
-
if (lines.length <= count) return lines
|
|
47
|
-
|
|
48
|
-
const cursorRow = lines.findIndex((line) => line.includes(CURSOR_MARKER))
|
|
49
|
-
if (cursorRow !== -1) {
|
|
50
|
-
const start = Math.max(
|
|
51
|
-
0,
|
|
52
|
-
Math.min(cursorRow - count + 1, lines.length - count),
|
|
53
|
-
)
|
|
54
|
-
return lines.slice(start, start + count)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return lines.slice(lines.length - count)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function extractCursor(lines: string[]): FixedEditorClusterRender {
|
|
61
|
-
let cursor: FixedEditorCursor | null = null
|
|
62
|
-
const cleaned = lines.map((line, row) => {
|
|
63
|
-
const markerIndex = line.indexOf(CURSOR_MARKER)
|
|
64
|
-
if (markerIndex === -1) return line
|
|
65
|
-
|
|
66
|
-
cursor ??= {
|
|
67
|
-
row,
|
|
68
|
-
col: visibleWidth(line.slice(0, markerIndex)),
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
line.slice(0, markerIndex) +
|
|
73
|
-
line.slice(markerIndex + CURSOR_MARKER.length)
|
|
74
|
-
)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
return { lines: cleaned, cursor }
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function renderFixedEditorCluster(
|
|
81
|
-
input: FixedEditorClusterInput,
|
|
82
|
-
): FixedEditorClusterRender {
|
|
83
|
-
const width = Math.max(1, input.width)
|
|
84
|
-
const maxRows = Math.max(1, input.terminalRows - 1)
|
|
85
|
-
|
|
86
|
-
const statusLines = normalizeLines(input.statusLines, width)
|
|
87
|
-
const aboveWidgetLines = normalizeLines(input.aboveWidgetLines, width)
|
|
88
|
-
const editorSource = normalizeLines(input.editorLines, width)
|
|
89
|
-
const belowWidgetLines = normalizeLines(input.belowWidgetLines, width)
|
|
90
|
-
const footerLines = normalizeLines(input.footerLines, width)
|
|
91
|
-
|
|
92
|
-
const editorLines = capEditorLines(editorSource, maxRows)
|
|
93
|
-
let remaining = maxRows - editorLines.length
|
|
94
|
-
|
|
95
|
-
const footer = takeTail(footerLines, remaining)
|
|
96
|
-
remaining -= footer.length
|
|
97
|
-
|
|
98
|
-
const belowWidgets = takeTail(belowWidgetLines, remaining)
|
|
99
|
-
remaining -= belowWidgets.length
|
|
100
|
-
|
|
101
|
-
const aboveWidgets = takeTail(aboveWidgetLines, remaining)
|
|
102
|
-
remaining -= aboveWidgets.length
|
|
103
|
-
|
|
104
|
-
const status = takeTail(statusLines, remaining)
|
|
105
|
-
|
|
106
|
-
return extractCursor([
|
|
107
|
-
...status,
|
|
108
|
-
...aboveWidgets,
|
|
109
|
-
...editorLines,
|
|
110
|
-
...belowWidgets,
|
|
111
|
-
...footer,
|
|
112
|
-
])
|
|
113
|
-
}
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
copyToClipboard,
|
|
3
|
-
type ExtensionAPI,
|
|
4
|
-
type ExtensionContext,
|
|
5
|
-
} from "@earendil-works/pi-coding-agent"
|
|
6
|
-
import type { Component } from "@earendil-works/pi-tui"
|
|
7
|
-
import { visibleWidth } from "@earendil-works/pi-tui"
|
|
8
|
-
import { renderFixedEditorCluster } from "./cluster.js"
|
|
9
|
-
import {
|
|
10
|
-
emergencyTerminalModeReset,
|
|
11
|
-
TerminalSplitCompositor,
|
|
12
|
-
type TerminalLike,
|
|
13
|
-
} from "./terminal-split.js"
|
|
14
|
-
|
|
15
|
-
const WIDGET_KEY = "pi-fixed-editor-probe"
|
|
16
|
-
const WARNING_MESSAGE = "pi-fixed-editor: unsupported Pi TUI layout"
|
|
17
|
-
|
|
18
|
-
interface ContainerLike extends Component {
|
|
19
|
-
children: Component[]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface TuiLike {
|
|
23
|
-
children: Component[]
|
|
24
|
-
terminal?: TerminalLike
|
|
25
|
-
focusedComponent?: Component | null
|
|
26
|
-
requestRender: (force?: boolean) => void
|
|
27
|
-
getShowHardwareCursor?: () => boolean
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface ContainerMatch {
|
|
31
|
-
container: ContainerLike
|
|
32
|
-
index: number
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let compositor: TerminalSplitCompositor | null = null
|
|
36
|
-
let isInstalled = false
|
|
37
|
-
let didWarnUnsupported = false
|
|
38
|
-
let fixedStatusContainer: Component | null = null
|
|
39
|
-
let fixedWidgetContainerAbove: Component | null = null
|
|
40
|
-
let fixedEditorContainer: Component | null = null
|
|
41
|
-
let fixedWidgetContainerBelow: Component | null = null
|
|
42
|
-
let fixedFooterContainer: Component | null = null
|
|
43
|
-
|
|
44
|
-
class ProbeComponent implements Component {
|
|
45
|
-
private readonly install: () => void
|
|
46
|
-
private hasQueuedInstall = false
|
|
47
|
-
|
|
48
|
-
constructor(onInstall: () => void) {
|
|
49
|
-
this.install = onInstall
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
render(): string[] {
|
|
53
|
-
if (!this.hasQueuedInstall) {
|
|
54
|
-
this.hasQueuedInstall = true
|
|
55
|
-
queueMicrotask(this.install)
|
|
56
|
-
}
|
|
57
|
-
return []
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
invalidate(): void {
|
|
61
|
-
this.hasQueuedInstall = false
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function isContainerLike(value: unknown): value is ContainerLike {
|
|
66
|
-
return (
|
|
67
|
-
typeof value === "object" &&
|
|
68
|
-
value !== null &&
|
|
69
|
-
Array.isArray(Reflect.get(value, "children")) &&
|
|
70
|
-
typeof Reflect.get(value, "render") === "function"
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function isRenderable(value: unknown): value is Component {
|
|
75
|
-
return (
|
|
76
|
-
typeof value === "object" &&
|
|
77
|
-
value !== null &&
|
|
78
|
-
typeof Reflect.get(value, "render") === "function" &&
|
|
79
|
-
typeof Reflect.get(value, "invalidate") === "function"
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function getTuiChildren(tui: TuiLike): Component[] {
|
|
84
|
-
return Array.isArray(tui.children) ? tui.children : []
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function findContainerWithChild(
|
|
88
|
-
tui: TuiLike,
|
|
89
|
-
child: Component,
|
|
90
|
-
): ContainerMatch | null {
|
|
91
|
-
const children = getTuiChildren(tui)
|
|
92
|
-
const index = children.findIndex(
|
|
93
|
-
(candidate) =>
|
|
94
|
-
isContainerLike(candidate) && candidate.children.includes(child),
|
|
95
|
-
)
|
|
96
|
-
if (index === -1) return null
|
|
97
|
-
|
|
98
|
-
const container = children[index]
|
|
99
|
-
return isContainerLike(container) ? { container, index } : null
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function findEditorContainer(tui: TuiLike): ContainerMatch | null {
|
|
103
|
-
const focusedComponent = Reflect.get(tui, "focusedComponent")
|
|
104
|
-
if (isRenderable(focusedComponent)) {
|
|
105
|
-
const match = findContainerWithChild(tui, focusedComponent)
|
|
106
|
-
if (match) return match
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const children = getTuiChildren(tui)
|
|
110
|
-
const index = children.findIndex((candidate) => {
|
|
111
|
-
if (!isContainerLike(candidate)) return false
|
|
112
|
-
return candidate.children.some((child) => {
|
|
113
|
-
return (
|
|
114
|
-
typeof Reflect.get(child, "getText") === "function" &&
|
|
115
|
-
typeof Reflect.get(child, "setText") === "function" &&
|
|
116
|
-
typeof Reflect.get(child, "handleInput") === "function"
|
|
117
|
-
)
|
|
118
|
-
})
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
if (index === -1) return null
|
|
122
|
-
const container = children[index]
|
|
123
|
-
return isContainerLike(container) ? { container, index } : null
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function renderHidden(
|
|
127
|
-
activeCompositor: TerminalSplitCompositor,
|
|
128
|
-
component: Component | null,
|
|
129
|
-
width: number,
|
|
130
|
-
): string[] {
|
|
131
|
-
if (!component) return []
|
|
132
|
-
return activeCompositor
|
|
133
|
-
.renderHidden(component, width)
|
|
134
|
-
.filter((line) => visibleWidth(line) > 0)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function resetContainers(): void {
|
|
138
|
-
fixedStatusContainer = null
|
|
139
|
-
fixedWidgetContainerAbove = null
|
|
140
|
-
fixedEditorContainer = null
|
|
141
|
-
fixedWidgetContainerBelow = null
|
|
142
|
-
fixedFooterContainer = null
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function teardown(options?: { resetExtendedKeyboardModes?: boolean }): void {
|
|
146
|
-
const hadCompositor = compositor !== null
|
|
147
|
-
compositor?.dispose(options)
|
|
148
|
-
if (!hadCompositor && options?.resetExtendedKeyboardModes) {
|
|
149
|
-
try {
|
|
150
|
-
process.stdout.write(emergencyTerminalModeReset())
|
|
151
|
-
} catch {
|
|
152
|
-
// Exit cleanup cannot surface useful terminal write failures.
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
compositor = null
|
|
156
|
-
isInstalled = false
|
|
157
|
-
resetContainers()
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function warnUnsupported(ctx: ExtensionContext): void {
|
|
161
|
-
if (didWarnUnsupported || ctx.mode !== "tui") return
|
|
162
|
-
didWarnUnsupported = true
|
|
163
|
-
ctx.ui.notify(WARNING_MESSAGE, "warning")
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function install(ctx: ExtensionContext, tui: TuiLike): void {
|
|
167
|
-
if (isInstalled || compositor) return
|
|
168
|
-
if (ctx.mode !== "tui") return
|
|
169
|
-
|
|
170
|
-
const terminal = tui.terminal
|
|
171
|
-
if (!terminal || typeof terminal.write !== "function") {
|
|
172
|
-
warnUnsupported(ctx)
|
|
173
|
-
return
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const editorMatch = findEditorContainer(tui)
|
|
177
|
-
if (!editorMatch) {
|
|
178
|
-
warnUnsupported(ctx)
|
|
179
|
-
return
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const children = getTuiChildren(tui)
|
|
183
|
-
fixedStatusContainer = children[editorMatch.index - 2] ?? null
|
|
184
|
-
fixedWidgetContainerAbove = children[editorMatch.index - 1] ?? null
|
|
185
|
-
fixedEditorContainer = editorMatch.container
|
|
186
|
-
fixedWidgetContainerBelow = children[editorMatch.index + 1] ?? null
|
|
187
|
-
fixedFooterContainer = children[editorMatch.index + 2] ?? null
|
|
188
|
-
|
|
189
|
-
let nextCompositor: TerminalSplitCompositor
|
|
190
|
-
nextCompositor = new TerminalSplitCompositor({
|
|
191
|
-
tui,
|
|
192
|
-
terminal,
|
|
193
|
-
onCopySelection: (text) => {
|
|
194
|
-
void copyToClipboard(text)
|
|
195
|
-
},
|
|
196
|
-
getShowHardwareCursor: () =>
|
|
197
|
-
typeof tui.getShowHardwareCursor === "function" &&
|
|
198
|
-
tui.getShowHardwareCursor(),
|
|
199
|
-
renderCluster: (width, terminalRows) =>
|
|
200
|
-
renderFixedEditorCluster({
|
|
201
|
-
width,
|
|
202
|
-
terminalRows,
|
|
203
|
-
statusLines: renderHidden(nextCompositor, fixedStatusContainer, width),
|
|
204
|
-
aboveWidgetLines: renderHidden(
|
|
205
|
-
nextCompositor,
|
|
206
|
-
fixedWidgetContainerAbove,
|
|
207
|
-
width,
|
|
208
|
-
),
|
|
209
|
-
editorLines: renderHidden(nextCompositor, fixedEditorContainer, width),
|
|
210
|
-
belowWidgetLines: renderHidden(
|
|
211
|
-
nextCompositor,
|
|
212
|
-
fixedWidgetContainerBelow,
|
|
213
|
-
width,
|
|
214
|
-
),
|
|
215
|
-
footerLines: renderHidden(nextCompositor, fixedFooterContainer, width),
|
|
216
|
-
}),
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
compositor = nextCompositor
|
|
220
|
-
if (fixedStatusContainer) nextCompositor.hideRenderable(fixedStatusContainer)
|
|
221
|
-
if (fixedWidgetContainerAbove)
|
|
222
|
-
nextCompositor.hideRenderable(fixedWidgetContainerAbove)
|
|
223
|
-
if (fixedEditorContainer) nextCompositor.hideRenderable(fixedEditorContainer)
|
|
224
|
-
if (fixedWidgetContainerBelow)
|
|
225
|
-
nextCompositor.hideRenderable(fixedWidgetContainerBelow)
|
|
226
|
-
if (fixedFooterContainer) nextCompositor.hideRenderable(fixedFooterContainer)
|
|
227
|
-
|
|
228
|
-
try {
|
|
229
|
-
nextCompositor.install()
|
|
230
|
-
isInstalled = true
|
|
231
|
-
tui.requestRender(true)
|
|
232
|
-
} catch {
|
|
233
|
-
teardown({ resetExtendedKeyboardModes: true })
|
|
234
|
-
warnUnsupported(ctx)
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export default function fixedEditor(pi: ExtensionAPI) {
|
|
239
|
-
pi.on("session_start", (_event, ctx) => {
|
|
240
|
-
didWarnUnsupported = false
|
|
241
|
-
if (ctx.mode !== "tui") return
|
|
242
|
-
|
|
243
|
-
ctx.ui.setWidget(
|
|
244
|
-
WIDGET_KEY,
|
|
245
|
-
(tui) => {
|
|
246
|
-
const tuiLike = tui as unknown as TuiLike
|
|
247
|
-
return new ProbeComponent(() => install(ctx, tuiLike))
|
|
248
|
-
},
|
|
249
|
-
{ placement: "aboveEditor" },
|
|
250
|
-
)
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
pi.on("session_shutdown", (_event, ctx) => {
|
|
254
|
-
if (ctx.mode === "tui") {
|
|
255
|
-
ctx.ui.setWidget(WIDGET_KEY, undefined)
|
|
256
|
-
}
|
|
257
|
-
teardown({ resetExtendedKeyboardModes: true })
|
|
258
|
-
})
|
|
259
|
-
}
|