kajji 0.1.0 → 0.1.1
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/bin/kajji +60 -0
- package/package.json +35 -55
- package/script/postinstall.mjs +50 -0
- package/LICENSE +0 -21
- package/README.md +0 -128
- package/bin/kajji.js +0 -2
- package/src/App.tsx +0 -229
- package/src/commander/bookmarks.ts +0 -129
- package/src/commander/diff.ts +0 -186
- package/src/commander/executor.ts +0 -285
- package/src/commander/files.ts +0 -87
- package/src/commander/log.ts +0 -99
- package/src/commander/operations.ts +0 -313
- package/src/commander/types.ts +0 -21
- package/src/components/AnsiText.tsx +0 -77
- package/src/components/BorderBox.tsx +0 -124
- package/src/components/FileTreeList.tsx +0 -105
- package/src/components/Layout.tsx +0 -48
- package/src/components/Panel.tsx +0 -143
- package/src/components/RevisionPicker.tsx +0 -165
- package/src/components/StatusBar.tsx +0 -158
- package/src/components/modals/BookmarkNameModal.tsx +0 -170
- package/src/components/modals/DescribeModal.tsx +0 -124
- package/src/components/modals/HelpModal.tsx +0 -372
- package/src/components/modals/RevisionPickerModal.tsx +0 -70
- package/src/components/modals/UndoModal.tsx +0 -75
- package/src/components/panels/BookmarksPanel.tsx +0 -768
- package/src/components/panels/CommandLogPanel.tsx +0 -40
- package/src/components/panels/LogPanel.tsx +0 -774
- package/src/components/panels/MainArea.tsx +0 -354
- package/src/context/command.tsx +0 -106
- package/src/context/commandlog.tsx +0 -45
- package/src/context/dialog.tsx +0 -217
- package/src/context/focus.tsx +0 -63
- package/src/context/helper.tsx +0 -24
- package/src/context/keybind.tsx +0 -51
- package/src/context/loading.tsx +0 -68
- package/src/context/sync.tsx +0 -868
- package/src/context/theme.tsx +0 -90
- package/src/context/types.ts +0 -51
- package/src/index.tsx +0 -15
- package/src/keybind/index.ts +0 -2
- package/src/keybind/parser.ts +0 -88
- package/src/keybind/types.ts +0 -83
- package/src/theme/index.ts +0 -3
- package/src/theme/presets/lazygit.ts +0 -45
- package/src/theme/presets/opencode.ts +0 -45
- package/src/theme/types.ts +0 -47
- package/src/utils/double-click.ts +0 -59
- package/src/utils/file-tree.ts +0 -154
package/src/commander/log.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { execute } from "./executor"
|
|
2
|
-
import type { Commit } from "./types"
|
|
3
|
-
|
|
4
|
-
const MARKER = "__LJ__"
|
|
5
|
-
|
|
6
|
-
// Strip ANSI escape codes from a string (for extracting clean metadata)
|
|
7
|
-
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ANSI escape sequence
|
|
8
|
-
const stripAnsi = (str: string) => str.replace(/\x1b\[[0-9;]*m/g, "")
|
|
9
|
-
|
|
10
|
-
function buildTemplate(): string {
|
|
11
|
-
const styledDescription = `if(empty, label("empty", "(empty) "), "") ++ if(description.first_line(), description.first_line(), label("description placeholder", "(no description set)"))`
|
|
12
|
-
|
|
13
|
-
const prefix = [
|
|
14
|
-
`"${MARKER}"`,
|
|
15
|
-
"change_id.short()",
|
|
16
|
-
`"${MARKER}"`,
|
|
17
|
-
"commit_id.short()",
|
|
18
|
-
`"${MARKER}"`,
|
|
19
|
-
"immutable",
|
|
20
|
-
`"${MARKER}"`,
|
|
21
|
-
"empty",
|
|
22
|
-
`"${MARKER}"`,
|
|
23
|
-
styledDescription,
|
|
24
|
-
`"${MARKER}"`,
|
|
25
|
-
"author.name()",
|
|
26
|
-
`"${MARKER}"`,
|
|
27
|
-
"author.email()",
|
|
28
|
-
`"${MARKER}"`,
|
|
29
|
-
'author.timestamp().local().format("%Y-%m-%d %H:%M:%S %:z")',
|
|
30
|
-
`"${MARKER}"`,
|
|
31
|
-
].join(" ++ ")
|
|
32
|
-
|
|
33
|
-
return `${prefix} ++ builtin_log_compact`
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function parseLogOutput(output: string): Commit[] {
|
|
37
|
-
const commits: Commit[] = []
|
|
38
|
-
let current: Commit | null = null
|
|
39
|
-
|
|
40
|
-
for (const line of output.split("\n")) {
|
|
41
|
-
if (line.includes(MARKER)) {
|
|
42
|
-
const parts = line.split(MARKER)
|
|
43
|
-
if (parts.length >= 10) {
|
|
44
|
-
if (current) {
|
|
45
|
-
commits.push(current)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const gutter = parts[0] ?? ""
|
|
49
|
-
current = {
|
|
50
|
-
changeId: stripAnsi(parts[1] ?? ""),
|
|
51
|
-
commitId: stripAnsi(parts[2] ?? ""),
|
|
52
|
-
immutable: stripAnsi(parts[3] ?? "") === "true",
|
|
53
|
-
empty: stripAnsi(parts[4] ?? "") === "true",
|
|
54
|
-
description: stripAnsi(parts[5] ?? ""),
|
|
55
|
-
author: stripAnsi(parts[6] ?? ""),
|
|
56
|
-
authorEmail: stripAnsi(parts[7] ?? ""),
|
|
57
|
-
timestamp: stripAnsi(parts[8] ?? ""),
|
|
58
|
-
isWorkingCopy: gutter.includes("@"),
|
|
59
|
-
lines: [gutter + (parts[9] ?? "")],
|
|
60
|
-
}
|
|
61
|
-
continue
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (current && line.trim() !== "") {
|
|
66
|
-
current.lines.push(line)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (current) {
|
|
71
|
-
commits.push(current)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return commits
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface FetchLogOptions {
|
|
78
|
-
cwd?: string
|
|
79
|
-
revset?: string
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export async function fetchLog(options?: FetchLogOptions): Promise<Commit[]> {
|
|
83
|
-
const template = buildTemplate()
|
|
84
|
-
const args = ["log", "--color", "always", "--template", template]
|
|
85
|
-
|
|
86
|
-
if (options?.revset) {
|
|
87
|
-
args.push("-r", options.revset)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const result = await execute(args, {
|
|
91
|
-
cwd: options?.cwd,
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
if (!result.success) {
|
|
95
|
-
throw new Error(`jj log failed: ${result.stderr}`)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return parseLogOutput(result.stdout)
|
|
99
|
-
}
|
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
import { type ExecuteResult, execute } from "./executor"
|
|
2
|
-
|
|
3
|
-
export interface OperationResult extends ExecuteResult {
|
|
4
|
-
command: string
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface OpLogEntry {
|
|
8
|
-
operationId: string
|
|
9
|
-
lines: string[]
|
|
10
|
-
isCurrent: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function stripAnsi(str: string): string {
|
|
14
|
-
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequence
|
|
15
|
-
return str.replace(/\x1b\[[0-9;]*m/g, "")
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function parseOpLog(lines: string[]): OpLogEntry[] {
|
|
19
|
-
const operations: OpLogEntry[] = []
|
|
20
|
-
let current: OpLogEntry | null = null
|
|
21
|
-
|
|
22
|
-
for (const line of lines) {
|
|
23
|
-
const stripped = stripAnsi(line)
|
|
24
|
-
const isHeader = stripped.startsWith("@") || stripped.startsWith("○")
|
|
25
|
-
|
|
26
|
-
if (isHeader) {
|
|
27
|
-
if (current) {
|
|
28
|
-
operations.push(current)
|
|
29
|
-
}
|
|
30
|
-
const parts = stripped.split(/\s+/)
|
|
31
|
-
const operationId = parts[1] || ""
|
|
32
|
-
current = {
|
|
33
|
-
operationId,
|
|
34
|
-
lines: [line],
|
|
35
|
-
isCurrent: stripped.startsWith("@"),
|
|
36
|
-
}
|
|
37
|
-
} else if (current && stripped.trim()) {
|
|
38
|
-
current.lines.push(line)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (current) {
|
|
43
|
-
operations.push(current)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return operations
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function jjNew(revision: string): Promise<OperationResult> {
|
|
50
|
-
const args = ["new", revision]
|
|
51
|
-
const result = await execute(args)
|
|
52
|
-
return {
|
|
53
|
-
...result,
|
|
54
|
-
command: `jj ${args.join(" ")}`,
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export async function jjEdit(revision: string): Promise<OperationResult> {
|
|
59
|
-
const args = ["edit", revision]
|
|
60
|
-
const result = await execute(args)
|
|
61
|
-
return {
|
|
62
|
-
...result,
|
|
63
|
-
command: `jj ${args.join(" ")}`,
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export async function jjSquash(
|
|
68
|
-
revision?: string,
|
|
69
|
-
options?: { ignoreImmutable?: boolean },
|
|
70
|
-
): Promise<OperationResult> {
|
|
71
|
-
const args = revision ? ["squash", "-r", revision] : ["squash"]
|
|
72
|
-
if (options?.ignoreImmutable) {
|
|
73
|
-
args.push("--ignore-immutable")
|
|
74
|
-
}
|
|
75
|
-
const result = await execute(args)
|
|
76
|
-
return {
|
|
77
|
-
...result,
|
|
78
|
-
command: `jj ${args.join(" ")}`,
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function isImmutableError(result: OperationResult): boolean {
|
|
83
|
-
return (
|
|
84
|
-
!result.success &&
|
|
85
|
-
(result.stderr.includes("immutable") || result.stderr.includes("Immutable"))
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export async function jjDescribe(
|
|
90
|
-
revision: string,
|
|
91
|
-
message: string,
|
|
92
|
-
options?: { ignoreImmutable?: boolean },
|
|
93
|
-
): Promise<OperationResult> {
|
|
94
|
-
const args = ["describe", revision, "-m", message]
|
|
95
|
-
if (options?.ignoreImmutable) {
|
|
96
|
-
args.push("--ignore-immutable")
|
|
97
|
-
}
|
|
98
|
-
const result = await execute(args)
|
|
99
|
-
return {
|
|
100
|
-
...result,
|
|
101
|
-
command: `jj describe ${revision} -m "..."`,
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export async function jjShowDescription(
|
|
106
|
-
revision: string,
|
|
107
|
-
): Promise<{ subject: string; body: string }> {
|
|
108
|
-
const result = await execute([
|
|
109
|
-
"log",
|
|
110
|
-
"-r",
|
|
111
|
-
revision,
|
|
112
|
-
"--no-graph",
|
|
113
|
-
"-T",
|
|
114
|
-
"description",
|
|
115
|
-
])
|
|
116
|
-
|
|
117
|
-
if (!result.success) {
|
|
118
|
-
return { subject: "", body: "" }
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const description = result.stdout.trim()
|
|
122
|
-
const lines = description.split("\n")
|
|
123
|
-
const subject = lines[0] ?? ""
|
|
124
|
-
const body = lines.slice(1).join("\n").trim()
|
|
125
|
-
|
|
126
|
-
return { subject, body }
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export async function jjShowDescriptionStyled(
|
|
130
|
-
revision: string,
|
|
131
|
-
): Promise<{ subject: string; body: string }> {
|
|
132
|
-
const styledTemplate = `if(empty, label("empty", "(empty) "), "") ++ if(description.first_line(), description.first_line(), label("description placeholder", "(no description set)"))`
|
|
133
|
-
const subjectResult = await execute([
|
|
134
|
-
"log",
|
|
135
|
-
"-r",
|
|
136
|
-
revision,
|
|
137
|
-
"--no-graph",
|
|
138
|
-
"--color",
|
|
139
|
-
"always",
|
|
140
|
-
"-T",
|
|
141
|
-
styledTemplate,
|
|
142
|
-
])
|
|
143
|
-
|
|
144
|
-
const bodyResult = await execute([
|
|
145
|
-
"log",
|
|
146
|
-
"-r",
|
|
147
|
-
revision,
|
|
148
|
-
"--no-graph",
|
|
149
|
-
"-T",
|
|
150
|
-
'if(description.first_line(), description.rest(), "")',
|
|
151
|
-
])
|
|
152
|
-
|
|
153
|
-
const subject = subjectResult.success ? subjectResult.stdout.trim() : ""
|
|
154
|
-
const body = bodyResult.success ? bodyResult.stdout.trim() : ""
|
|
155
|
-
|
|
156
|
-
return { subject, body }
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export async function jjAbandon(revision: string): Promise<OperationResult> {
|
|
160
|
-
const args = ["abandon", revision]
|
|
161
|
-
const result = await execute(args)
|
|
162
|
-
return {
|
|
163
|
-
...result,
|
|
164
|
-
command: `jj ${args.join(" ")}`,
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export async function fetchOpLog(limit?: number): Promise<string[]> {
|
|
169
|
-
const args = ["op", "log", "--color", "always"]
|
|
170
|
-
if (limit) {
|
|
171
|
-
args.push("--limit", String(limit))
|
|
172
|
-
}
|
|
173
|
-
const result = await execute(args)
|
|
174
|
-
if (!result.success) {
|
|
175
|
-
throw new Error(`jj op log failed: ${result.stderr}`)
|
|
176
|
-
}
|
|
177
|
-
return result.stdout.split("\n")
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export async function fetchOpLogId(): Promise<string> {
|
|
181
|
-
const result = await execute([
|
|
182
|
-
"op",
|
|
183
|
-
"log",
|
|
184
|
-
"--limit",
|
|
185
|
-
"1",
|
|
186
|
-
"--no-graph",
|
|
187
|
-
"-T",
|
|
188
|
-
"self.id()",
|
|
189
|
-
])
|
|
190
|
-
return result.success ? result.stdout.trim() : ""
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export async function jjUndo(): Promise<OperationResult> {
|
|
194
|
-
const args = ["undo"]
|
|
195
|
-
const result = await execute(args)
|
|
196
|
-
return {
|
|
197
|
-
...result,
|
|
198
|
-
command: "jj undo",
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export async function jjRedo(): Promise<OperationResult> {
|
|
203
|
-
const args = ["redo"]
|
|
204
|
-
const result = await execute(args)
|
|
205
|
-
return {
|
|
206
|
-
...result,
|
|
207
|
-
command: "jj redo",
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export async function jjOpRestore(
|
|
212
|
-
operationId: string,
|
|
213
|
-
): Promise<OperationResult> {
|
|
214
|
-
const args = ["op", "restore", operationId]
|
|
215
|
-
const result = await execute(args)
|
|
216
|
-
return {
|
|
217
|
-
...result,
|
|
218
|
-
command: `jj op restore ${operationId}`,
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export async function jjGitFetch(options?: {
|
|
223
|
-
allRemotes?: boolean
|
|
224
|
-
}): Promise<OperationResult> {
|
|
225
|
-
const args = ["git", "fetch"]
|
|
226
|
-
if (options?.allRemotes) {
|
|
227
|
-
args.push("--all-remotes")
|
|
228
|
-
}
|
|
229
|
-
const result = await execute(args)
|
|
230
|
-
return {
|
|
231
|
-
...result,
|
|
232
|
-
command: `jj ${args.join(" ")}`,
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export async function jjGitPush(options?: {
|
|
237
|
-
all?: boolean
|
|
238
|
-
}): Promise<OperationResult> {
|
|
239
|
-
const args = ["git", "push"]
|
|
240
|
-
if (options?.all) {
|
|
241
|
-
args.push("--all")
|
|
242
|
-
}
|
|
243
|
-
const result = await execute(args)
|
|
244
|
-
return {
|
|
245
|
-
...result,
|
|
246
|
-
command: `jj ${args.join(" ")}`,
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export async function jjRestore(
|
|
251
|
-
paths: string[],
|
|
252
|
-
revision?: string,
|
|
253
|
-
): Promise<OperationResult> {
|
|
254
|
-
const args = ["restore"]
|
|
255
|
-
if (revision) {
|
|
256
|
-
args.push("-r", revision)
|
|
257
|
-
}
|
|
258
|
-
args.push(...paths)
|
|
259
|
-
const result = await execute(args)
|
|
260
|
-
return {
|
|
261
|
-
...result,
|
|
262
|
-
command: `jj ${args.join(" ")}`,
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
export interface DiffStats {
|
|
267
|
-
files: { path: string; insertions: number; deletions: number }[]
|
|
268
|
-
totalFiles: number
|
|
269
|
-
totalInsertions: number
|
|
270
|
-
totalDeletions: number
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
export async function jjDiffStats(revision: string): Promise<DiffStats> {
|
|
274
|
-
const result = await execute(["diff", "--stat", "-r", revision])
|
|
275
|
-
|
|
276
|
-
if (!result.success) {
|
|
277
|
-
return { files: [], totalFiles: 0, totalInsertions: 0, totalDeletions: 0 }
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const lines = result.stdout.trim().split("\n")
|
|
281
|
-
const files: DiffStats["files"] = []
|
|
282
|
-
let totalFiles = 0
|
|
283
|
-
let totalInsertions = 0
|
|
284
|
-
let totalDeletions = 0
|
|
285
|
-
|
|
286
|
-
for (const line of lines) {
|
|
287
|
-
// Summary line: "14 files changed, 1448 insertions(+), 56 deletions(-)"
|
|
288
|
-
const summaryMatch = line.match(
|
|
289
|
-
/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/,
|
|
290
|
-
)
|
|
291
|
-
if (summaryMatch) {
|
|
292
|
-
totalFiles = Number.parseInt(summaryMatch[1] ?? "0", 10)
|
|
293
|
-
totalInsertions = summaryMatch[2]
|
|
294
|
-
? Number.parseInt(summaryMatch[2], 10)
|
|
295
|
-
: 0
|
|
296
|
-
totalDeletions = summaryMatch[3]
|
|
297
|
-
? Number.parseInt(summaryMatch[3], 10)
|
|
298
|
-
: 0
|
|
299
|
-
continue
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// File line: "src/foo.ts | 12 ++++----"
|
|
303
|
-
const fileMatch = line.match(/^(.+?)\s+\|\s+(\d+)\s+([+-]*)/)
|
|
304
|
-
if (fileMatch) {
|
|
305
|
-
const path = (fileMatch[1] ?? "").trim()
|
|
306
|
-
const plusCount = (fileMatch[3]?.match(/\+/g) || []).length
|
|
307
|
-
const minusCount = (fileMatch[3]?.match(/-/g) || []).length
|
|
308
|
-
files.push({ path, insertions: plusCount, deletions: minusCount })
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return { files, totalFiles, totalInsertions, totalDeletions }
|
|
313
|
-
}
|
package/src/commander/types.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export interface Commit {
|
|
2
|
-
changeId: string
|
|
3
|
-
commitId: string
|
|
4
|
-
description: string
|
|
5
|
-
author: string
|
|
6
|
-
authorEmail: string
|
|
7
|
-
timestamp: string
|
|
8
|
-
lines: string[]
|
|
9
|
-
isWorkingCopy: boolean
|
|
10
|
-
immutable: boolean
|
|
11
|
-
empty: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export type FileStatus = "added" | "modified" | "deleted" | "renamed" | "copied"
|
|
15
|
-
|
|
16
|
-
export interface FileChange {
|
|
17
|
-
path: string
|
|
18
|
-
status: FileStatus
|
|
19
|
-
/** Original path for renamed/copied files */
|
|
20
|
-
oldPath?: string
|
|
21
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import type { TerminalLine } from "ghostty-opentui"
|
|
2
|
-
import { ptyToJson } from "ghostty-opentui"
|
|
3
|
-
import { For, Show, createEffect, createMemo } from "solid-js"
|
|
4
|
-
|
|
5
|
-
const PROFILE = process.env.KAJJI_PROFILE === "1"
|
|
6
|
-
|
|
7
|
-
function profile(label: string) {
|
|
8
|
-
if (!PROFILE) return () => {}
|
|
9
|
-
const start = performance.now()
|
|
10
|
-
return (extra?: string) => {
|
|
11
|
-
const ms = (performance.now() - start).toFixed(2)
|
|
12
|
-
console.error(`[PROFILE] ${label}: ${ms}ms${extra ? ` (${extra})` : ""}`)
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface AnsiTextProps {
|
|
17
|
-
content: string
|
|
18
|
-
cols?: number
|
|
19
|
-
bold?: boolean
|
|
20
|
-
wrapMode?: "none" | "char" | "word"
|
|
21
|
-
maxLines?: number
|
|
22
|
-
onTotalLines?: (total: number) => void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function AnsiText(props: AnsiTextProps) {
|
|
26
|
-
const allLines = createMemo(() => {
|
|
27
|
-
if (!props.content) return [] as TerminalLine[]
|
|
28
|
-
const endParse = profile("ptyToJson parse")
|
|
29
|
-
const result = ptyToJson(props.content, {
|
|
30
|
-
cols: props.cols ?? 9999,
|
|
31
|
-
rows: 1,
|
|
32
|
-
})
|
|
33
|
-
endParse(`${result.lines.length} lines from ${props.content.length} chars`)
|
|
34
|
-
return result.lines
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
createEffect(() => {
|
|
38
|
-
const total = allLines().length
|
|
39
|
-
props.onTotalLines?.(total)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
const visibleLines = createMemo(() => {
|
|
43
|
-
const lines = allLines()
|
|
44
|
-
const limit = props.maxLines
|
|
45
|
-
if (limit !== undefined && lines.length > limit) {
|
|
46
|
-
return lines.slice(0, limit)
|
|
47
|
-
}
|
|
48
|
-
return lines
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const renderSpans = (line: TerminalLine) => (
|
|
52
|
-
<For each={line.spans}>
|
|
53
|
-
{(span) => (
|
|
54
|
-
<span
|
|
55
|
-
style={{
|
|
56
|
-
fg: span.fg ?? undefined,
|
|
57
|
-
bg: span.bg ?? undefined,
|
|
58
|
-
}}
|
|
59
|
-
>
|
|
60
|
-
{span.text}
|
|
61
|
-
</span>
|
|
62
|
-
)}
|
|
63
|
-
</For>
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<For each={visibleLines()}>
|
|
68
|
-
{(line) => (
|
|
69
|
-
<text wrapMode={props.wrapMode ?? "word"}>
|
|
70
|
-
<Show when={props.bold} fallback={renderSpans(line)}>
|
|
71
|
-
<b>{renderSpans(line)}</b>
|
|
72
|
-
</Show>
|
|
73
|
-
</text>
|
|
74
|
-
)}
|
|
75
|
-
</For>
|
|
76
|
-
)
|
|
77
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import type { BorderStyle } from "@opentui/core"
|
|
2
|
-
import type { JSX } from "solid-js"
|
|
3
|
-
import { Show, children as resolveChildren } from "solid-js"
|
|
4
|
-
|
|
5
|
-
type Dimension = number | "auto" | `${number}%`
|
|
6
|
-
type CornerContent = JSX.Element | string | (() => JSX.Element | string)
|
|
7
|
-
|
|
8
|
-
interface BorderBoxProps {
|
|
9
|
-
topLeft?: CornerContent
|
|
10
|
-
topRight?: CornerContent
|
|
11
|
-
bottomLeft?: CornerContent
|
|
12
|
-
bottomRight?: CornerContent
|
|
13
|
-
|
|
14
|
-
border?: boolean
|
|
15
|
-
borderStyle?: BorderStyle
|
|
16
|
-
borderColor?: string
|
|
17
|
-
backgroundColor?: string
|
|
18
|
-
flexGrow?: number
|
|
19
|
-
flexDirection?: "row" | "column"
|
|
20
|
-
width?: Dimension
|
|
21
|
-
height?: Dimension
|
|
22
|
-
padding?: number
|
|
23
|
-
paddingLeft?: number
|
|
24
|
-
paddingRight?: number
|
|
25
|
-
paddingTop?: number
|
|
26
|
-
paddingBottom?: number
|
|
27
|
-
gap?: number
|
|
28
|
-
overflow?: "hidden" | "visible"
|
|
29
|
-
onMouseDown?: () => void
|
|
30
|
-
|
|
31
|
-
children: JSX.Element
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function BorderBox(props: BorderBoxProps) {
|
|
35
|
-
const resolved = resolveChildren(() => props.children)
|
|
36
|
-
|
|
37
|
-
const hasOverlays = () =>
|
|
38
|
-
props.topLeft || props.topRight || props.bottomLeft || props.bottomRight
|
|
39
|
-
|
|
40
|
-
const resolveCorner = (content: CornerContent | undefined) =>
|
|
41
|
-
typeof content === "function" ? content() : content
|
|
42
|
-
|
|
43
|
-
const renderCorner = (
|
|
44
|
-
position: "topLeft" | "topRight" | "bottomLeft" | "bottomRight",
|
|
45
|
-
) => {
|
|
46
|
-
const content = resolveCorner(props[position])
|
|
47
|
-
if (!content) return null
|
|
48
|
-
|
|
49
|
-
const isTop = position.startsWith("top")
|
|
50
|
-
const isLeft = position.endsWith("Left")
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<box
|
|
54
|
-
position="absolute"
|
|
55
|
-
top={isTop ? 0 : undefined}
|
|
56
|
-
bottom={!isTop ? 0 : undefined}
|
|
57
|
-
left={isLeft ? 1 : undefined}
|
|
58
|
-
right={!isLeft ? 1 : undefined}
|
|
59
|
-
zIndex={1}
|
|
60
|
-
>
|
|
61
|
-
{typeof content === "string" ? <text>{content}</text> : content}
|
|
62
|
-
</box>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!hasOverlays()) {
|
|
67
|
-
return (
|
|
68
|
-
<box
|
|
69
|
-
flexDirection={props.flexDirection ?? "column"}
|
|
70
|
-
flexGrow={props.flexGrow}
|
|
71
|
-
width={props.width}
|
|
72
|
-
height={props.height}
|
|
73
|
-
border={props.border}
|
|
74
|
-
borderStyle={props.borderStyle}
|
|
75
|
-
borderColor={props.borderColor}
|
|
76
|
-
backgroundColor={props.backgroundColor}
|
|
77
|
-
padding={props.padding}
|
|
78
|
-
paddingLeft={props.paddingLeft}
|
|
79
|
-
paddingRight={props.paddingRight}
|
|
80
|
-
paddingTop={props.paddingTop}
|
|
81
|
-
paddingBottom={props.paddingBottom}
|
|
82
|
-
gap={props.gap}
|
|
83
|
-
overflow={props.overflow}
|
|
84
|
-
onMouseDown={props.onMouseDown}
|
|
85
|
-
>
|
|
86
|
-
{resolved()}
|
|
87
|
-
</box>
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<box
|
|
93
|
-
position="relative"
|
|
94
|
-
flexDirection="column"
|
|
95
|
-
flexGrow={props.flexGrow}
|
|
96
|
-
width={props.width}
|
|
97
|
-
height={props.height}
|
|
98
|
-
onMouseDown={props.onMouseDown}
|
|
99
|
-
>
|
|
100
|
-
<Show when={props.topLeft}>{() => renderCorner("topLeft")}</Show>
|
|
101
|
-
<Show when={props.topRight}>{() => renderCorner("topRight")}</Show>
|
|
102
|
-
<Show when={props.bottomLeft}>{() => renderCorner("bottomLeft")}</Show>
|
|
103
|
-
<Show when={props.bottomRight}>{() => renderCorner("bottomRight")}</Show>
|
|
104
|
-
|
|
105
|
-
<box
|
|
106
|
-
flexDirection={props.flexDirection ?? "column"}
|
|
107
|
-
flexGrow={props.flexGrow}
|
|
108
|
-
border={props.border}
|
|
109
|
-
borderStyle={props.borderStyle}
|
|
110
|
-
borderColor={props.borderColor}
|
|
111
|
-
backgroundColor={props.backgroundColor}
|
|
112
|
-
padding={props.padding}
|
|
113
|
-
paddingLeft={props.paddingLeft}
|
|
114
|
-
paddingRight={props.paddingRight}
|
|
115
|
-
paddingTop={props.paddingTop}
|
|
116
|
-
paddingBottom={props.paddingBottom}
|
|
117
|
-
gap={props.gap}
|
|
118
|
-
overflow={props.overflow}
|
|
119
|
-
>
|
|
120
|
-
{resolved()}
|
|
121
|
-
</box>
|
|
122
|
-
</box>
|
|
123
|
-
)
|
|
124
|
-
}
|