opencode-git-plugin 0.2.0 → 0.3.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/README.md +8 -3
- package/dist/tui.js +2123 -0
- package/package.json +5 -4
- package/src/config/constants.ts +0 -16
- package/src/config/index.ts +0 -2
- package/src/config/options.ts +0 -8
- package/src/diff/body.tsx +0 -90
- package/src/diff/header.tsx +0 -77
- package/src/diff/index.ts +0 -2
- package/src/diff/keymap.ts +0 -113
- package/src/diff/open-diff.ts +0 -57
- package/src/diff/panel.tsx +0 -156
- package/src/diff/search.tsx +0 -38
- package/src/git/diff-parse.ts +0 -35
- package/src/git/index.ts +0 -3
- package/src/git/porcelain.ts +0 -10
- package/src/git/runner.ts +0 -45
- package/src/sidebar/file-list.tsx +0 -47
- package/src/sidebar/focus.tsx +0 -37
- package/src/sidebar/header.tsx +0 -55
- package/src/sidebar/index.ts +0 -1
- package/src/sidebar/keymap.ts +0 -143
- package/src/sidebar/list-navigation.tsx +0 -96
- package/src/sidebar/staging.tsx +0 -108
- package/src/sidebar/status.tsx +0 -29
- package/src/sidebar/view.tsx +0 -159
- package/src/sidebar/working-copy.tsx +0 -81
- package/src/state/diff-store.ts +0 -34
- package/src/state/index.ts +0 -1
- package/src/tui.tsx +0 -25
- package/src/types.ts +0 -63
- package/src/ui/format.ts +0 -67
- package/src/ui/index.ts +0 -2
- package/src/ui/syntax.ts +0 -61
- package/tui.ts +0 -1
package/src/sidebar/view.tsx
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
/** @jsxImportSource @opentui/solid */
|
|
2
|
-
import { createEffect, createSignal, onCleanup, Show } from "solid-js"
|
|
3
|
-
import type { Context } from "@opencode/plugin/tui/context"
|
|
4
|
-
import { createDiffOpener } from "../diff"
|
|
5
|
-
import { createGitRunner } from "../git"
|
|
6
|
-
import { setDiffClosedHandler, useDiffStore } from "../state"
|
|
7
|
-
import type { ChangeFile, LayoutResizeEmitter, RenderNode } from "../types"
|
|
8
|
-
import { FileList } from "./file-list"
|
|
9
|
-
import { bindSidebarFocus } from "./focus"
|
|
10
|
-
import { SidebarHeader } from "./header"
|
|
11
|
-
import { registerSidebarKeymaps } from "./keymap"
|
|
12
|
-
import { createListNavigation } from "./list-navigation"
|
|
13
|
-
import { createStaging } from "./staging"
|
|
14
|
-
import { SidebarStatus } from "./status"
|
|
15
|
-
import { createWorkingCopy } from "./working-copy"
|
|
16
|
-
|
|
17
|
-
export function GitChangesView(props: { ctx: Context }) {
|
|
18
|
-
const { ctx } = props
|
|
19
|
-
const [sidebarWidth, setSidebarWidth] = createSignal(46)
|
|
20
|
-
const [diffView] = useDiffStore(ctx)
|
|
21
|
-
let sidebarNode: RenderNode | undefined
|
|
22
|
-
|
|
23
|
-
const currentLocation = () => {
|
|
24
|
-
const loc = ctx.location ?? ctx.data.location.default()
|
|
25
|
-
return { directory: loc.directory }
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const git = createGitRunner(ctx, () => currentLocation().directory)
|
|
29
|
-
const openDiff = createDiffOpener(ctx, currentLocation)
|
|
30
|
-
const workingCopy = createWorkingCopy(ctx, currentLocation, (count) => {
|
|
31
|
-
nav.clampSelection(count)
|
|
32
|
-
void staging.scheduleRefresh()
|
|
33
|
-
})
|
|
34
|
-
const staging = createStaging(ctx, git, currentLocation, workingCopy.refresh)
|
|
35
|
-
const nav = createListNavigation(ctx, workingCopy.files)
|
|
36
|
-
|
|
37
|
-
const openSelectedDiff = () => {
|
|
38
|
-
const f = nav.selectedFile()
|
|
39
|
-
if (f) void openDiff(f).catch(() => {})
|
|
40
|
-
}
|
|
41
|
-
const setSelectedFileStaged = (stage: boolean) => {
|
|
42
|
-
const f = nav.selectedFile()
|
|
43
|
-
if (f) staging.applyChange(f.file, stage)
|
|
44
|
-
}
|
|
45
|
-
const selectFile = (f: ChangeFile, index: number) => {
|
|
46
|
-
nav.setSelected(index)
|
|
47
|
-
if (nav.stageMode()) staging.toggle(f.file)
|
|
48
|
-
else void openDiff(f).catch(() => {})
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
void workingCopy.refresh().catch(() => {})
|
|
52
|
-
void staging.scheduleRefresh().catch(() => {})
|
|
53
|
-
|
|
54
|
-
registerSidebarKeymaps(ctx, {
|
|
55
|
-
files: workingCopy.files,
|
|
56
|
-
nav,
|
|
57
|
-
diffOpen: () => diffView.panelOpen,
|
|
58
|
-
requestRefresh: workingCopy.requestRefresh,
|
|
59
|
-
actions: {
|
|
60
|
-
next: nav.selectNext,
|
|
61
|
-
prev: nav.selectPrevious,
|
|
62
|
-
open: openSelectedDiff,
|
|
63
|
-
setStaged: setSelectedFileStaged,
|
|
64
|
-
commit: () => void staging.commit(),
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
const restoreListFocus = () => {
|
|
69
|
-
if (!nav.navMode() || diffView.panelOpen) return
|
|
70
|
-
nav.ensure()
|
|
71
|
-
}
|
|
72
|
-
setDiffClosedHandler(() => {
|
|
73
|
-
if (!nav.navMode() && workingCopy.files().length > 0) nav.enter()
|
|
74
|
-
restoreListFocus()
|
|
75
|
-
})
|
|
76
|
-
createEffect((wasOpen: boolean | undefined) => {
|
|
77
|
-
const open = diffView.panelOpen
|
|
78
|
-
if (wasOpen === true && open === false && nav.navMode()) {
|
|
79
|
-
setTimeout(() => restoreListFocus(), 0)
|
|
80
|
-
}
|
|
81
|
-
return open
|
|
82
|
-
})
|
|
83
|
-
bindSidebarFocus(ctx, {
|
|
84
|
-
container: () => sidebarNode,
|
|
85
|
-
active: () => nav.navMode() && !diffView.panelOpen,
|
|
86
|
-
exit: nav.exit,
|
|
87
|
-
})
|
|
88
|
-
onCleanup(() => setDiffClosedHandler(undefined))
|
|
89
|
-
|
|
90
|
-
const setSidebarRef = (el: RenderNode) => {
|
|
91
|
-
sidebarNode = el
|
|
92
|
-
setSidebarWidth(el.width)
|
|
93
|
-
const onNodeResize = () => setSidebarWidth(el.width)
|
|
94
|
-
;(el as unknown as LayoutResizeEmitter).on("resize", onNodeResize)
|
|
95
|
-
onCleanup(() => (el as unknown as LayoutResizeEmitter).off("resize", onNodeResize))
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const ready = () => !workingCopy.loading() && !workingCopy.error()
|
|
99
|
-
const hasFiles = () => workingCopy.files().length > 0
|
|
100
|
-
|
|
101
|
-
const sidebarBody = (
|
|
102
|
-
<>
|
|
103
|
-
<SidebarHeader
|
|
104
|
-
ctx={ctx}
|
|
105
|
-
branch={workingCopy.branch()}
|
|
106
|
-
fileCount={workingCopy.files().length}
|
|
107
|
-
ready={ready()}
|
|
108
|
-
additions={workingCopy.additions()}
|
|
109
|
-
deletions={workingCopy.deletions()}
|
|
110
|
-
navMode={nav.navMode()}
|
|
111
|
-
stageMode={nav.stageMode()}
|
|
112
|
-
hasStaged={staging.staged().size > 0}
|
|
113
|
-
onBranchClick={nav.toggleStageMode}
|
|
114
|
-
onCommit={() => void staging.commit()}
|
|
115
|
-
/>
|
|
116
|
-
<SidebarStatus
|
|
117
|
-
ctx={ctx}
|
|
118
|
-
loading={workingCopy.loading()}
|
|
119
|
-
error={workingCopy.error()}
|
|
120
|
-
hasFiles={hasFiles()}
|
|
121
|
-
branch={workingCopy.branch()}
|
|
122
|
-
/>
|
|
123
|
-
<Show when={ready() && hasFiles()}>
|
|
124
|
-
<FileList
|
|
125
|
-
ctx={ctx}
|
|
126
|
-
files={workingCopy.files()}
|
|
127
|
-
staged={staging.staged()}
|
|
128
|
-
selected={nav.selected()}
|
|
129
|
-
navMode={nav.navMode()}
|
|
130
|
-
width={sidebarWidth()}
|
|
131
|
-
onSelect={selectFile}
|
|
132
|
-
/>
|
|
133
|
-
</Show>
|
|
134
|
-
</>
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<Show
|
|
139
|
-
when={nav.navMode()}
|
|
140
|
-
fallback={
|
|
141
|
-
<box flexDirection="column" gap={1} ref={setSidebarRef}>
|
|
142
|
-
{sidebarBody}
|
|
143
|
-
</box>
|
|
144
|
-
}
|
|
145
|
-
>
|
|
146
|
-
<box
|
|
147
|
-
flexDirection="column"
|
|
148
|
-
gap={1}
|
|
149
|
-
border={["top", "bottom"]}
|
|
150
|
-
borderStyle="rounded"
|
|
151
|
-
borderColor={ctx.theme.text.feedback.warning.base}
|
|
152
|
-
focusedBorderColor={ctx.theme.text.feedback.warning.base}
|
|
153
|
-
ref={setSidebarRef}
|
|
154
|
-
>
|
|
155
|
-
{sidebarBody}
|
|
156
|
-
</box>
|
|
157
|
-
</Show>
|
|
158
|
-
)
|
|
159
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"
|
|
2
|
-
import type { Context } from "@opencode/plugin/tui/context"
|
|
3
|
-
import { EVENT_REFRESH_DEBOUNCE_MS, REFRESH_STORE_KEY, intervalMs } from "../config"
|
|
4
|
-
import { friendlyError } from "../ui"
|
|
5
|
-
import type { ChangeFile } from "../types"
|
|
6
|
-
|
|
7
|
-
export type WorkingCopy = ReturnType<typeof createWorkingCopy>
|
|
8
|
-
|
|
9
|
-
export function createWorkingCopy(
|
|
10
|
-
ctx: Context,
|
|
11
|
-
location: () => { directory: string },
|
|
12
|
-
onRefreshed: (count: number) => void,
|
|
13
|
-
) {
|
|
14
|
-
const [files, setFiles] = createSignal<ChangeFile[]>([])
|
|
15
|
-
const [branch, setBranch] = createSignal<string>("")
|
|
16
|
-
const [loading, setLoading] = createSignal(true)
|
|
17
|
-
const [error, setError] = createSignal<string | undefined>(undefined)
|
|
18
|
-
|
|
19
|
-
let isRefreshing = false
|
|
20
|
-
let lastEventRefreshAt = 0
|
|
21
|
-
|
|
22
|
-
const refresh = async () => {
|
|
23
|
-
if (isRefreshing) return
|
|
24
|
-
isRefreshing = true
|
|
25
|
-
try {
|
|
26
|
-
const loc = location()
|
|
27
|
-
const [statusRes, infoRes] = await Promise.all([
|
|
28
|
-
ctx.client.vcs.status({ location: loc }),
|
|
29
|
-
ctx.client.vcs.get({ location: loc }),
|
|
30
|
-
])
|
|
31
|
-
const prevByFile = new Map(files().map((f) => [f.file, f]))
|
|
32
|
-
setFiles(
|
|
33
|
-
statusRes.data.map((d) => {
|
|
34
|
-
const p = prevByFile.get(d.file)
|
|
35
|
-
return p && p.additions === d.additions && p.deletions === d.deletions && p.status === d.status
|
|
36
|
-
? p
|
|
37
|
-
: { ...d }
|
|
38
|
-
}),
|
|
39
|
-
)
|
|
40
|
-
setBranch(infoRes.data.branch.current ?? "")
|
|
41
|
-
setError(undefined)
|
|
42
|
-
onRefreshed(statusRes.data.length)
|
|
43
|
-
} catch (e) {
|
|
44
|
-
setError(friendlyError(e))
|
|
45
|
-
} finally {
|
|
46
|
-
setLoading(false)
|
|
47
|
-
isRefreshing = false
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const additions = createMemo(() => files().reduce((n, f) => n + f.additions, 0))
|
|
52
|
-
const deletions = createMemo(() => files().reduce((n, f) => n + f.deletions, 0))
|
|
53
|
-
|
|
54
|
-
const [refreshSignal, bumpRefresh] = ctx.storage.memory(REFRESH_STORE_KEY, { initial: { nonce: 0 } })
|
|
55
|
-
const requestRefresh = () =>
|
|
56
|
-
bumpRefresh((draft) => {
|
|
57
|
-
draft.nonce += 1
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
createEffect((prev: number | undefined) => {
|
|
61
|
-
const nonce = refreshSignal.nonce
|
|
62
|
-
if (prev !== undefined) void refresh().catch(() => {})
|
|
63
|
-
return nonce
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
const timer = setInterval(() => void refresh().catch(() => {}), intervalMs(ctx))
|
|
67
|
-
const stopListening = ctx.data.listen(({ details }: { details: { type: string } }) => {
|
|
68
|
-
const t = details.type
|
|
69
|
-
if (!(t.includes("session") || t.includes("vcs") || t.includes("server.connected") || t.includes("location"))) return
|
|
70
|
-
const now = Date.now()
|
|
71
|
-
if (now - lastEventRefreshAt < EVENT_REFRESH_DEBOUNCE_MS) return
|
|
72
|
-
lastEventRefreshAt = now
|
|
73
|
-
void refresh().catch(() => {})
|
|
74
|
-
})
|
|
75
|
-
onCleanup(() => {
|
|
76
|
-
clearInterval(timer)
|
|
77
|
-
stopListening()
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
return { files, branch, loading, error, additions, deletions, refresh, requestRefresh }
|
|
81
|
-
}
|
package/src/state/diff-store.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { Context } from "@opencode/plugin/tui/context"
|
|
2
|
-
import { DIFF_STORE_KEY } from "../config"
|
|
3
|
-
import type { DiffViewState } from "../types"
|
|
4
|
-
|
|
5
|
-
export const initialDiffState: DiffViewState = {
|
|
6
|
-
file: "",
|
|
7
|
-
additions: 0,
|
|
8
|
-
deletions: 0,
|
|
9
|
-
lines: [],
|
|
10
|
-
patch: "",
|
|
11
|
-
message: "",
|
|
12
|
-
truncated: false,
|
|
13
|
-
loading: false,
|
|
14
|
-
query: "",
|
|
15
|
-
matchCursor: 0,
|
|
16
|
-
panelOpen: false,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function useDiffStore(ctx: Context) {
|
|
20
|
-
return ctx.storage.memory(DIFF_STORE_KEY, { initial: initialDiffState })
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let diffClosedHandler: (() => void) | undefined
|
|
24
|
-
|
|
25
|
-
export function setDiffClosedHandler(handler: (() => void) | undefined): void {
|
|
26
|
-
diffClosedHandler = handler
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function notifyDiffClosed(): void {
|
|
30
|
-
try {
|
|
31
|
-
diffClosedHandler?.()
|
|
32
|
-
} catch {
|
|
33
|
-
}
|
|
34
|
-
}
|
package/src/state/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./diff-store"
|
package/src/tui.tsx
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/** @jsxImportSource @opentui/solid */
|
|
2
|
-
import { Plugin } from "@opencode/plugin/tui"
|
|
3
|
-
import { DiffPanel } from "./diff"
|
|
4
|
-
import { GitChangesView } from "./sidebar"
|
|
5
|
-
|
|
6
|
-
export { parseDiff, parseStagedPorcelain } from "./git"
|
|
7
|
-
|
|
8
|
-
export default Plugin.define({
|
|
9
|
-
id: "git-panel.cli",
|
|
10
|
-
setup(ctx) {
|
|
11
|
-
const unregisterSidebar = ctx.ui.slot({
|
|
12
|
-
append: "sidebar.content",
|
|
13
|
-
render: () => <GitChangesView ctx={ctx} />,
|
|
14
|
-
})
|
|
15
|
-
const unregisterPanel = ctx.ui.slot({
|
|
16
|
-
append: "session.panel",
|
|
17
|
-
render: (input) => <DiffPanel ctx={ctx} input={input} />,
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
return () => {
|
|
21
|
-
unregisterSidebar()
|
|
22
|
-
unregisterPanel()
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
})
|
package/src/types.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type { Context } from "@opencode/plugin/tui/context"
|
|
2
|
-
|
|
3
|
-
export type RenderNode = {
|
|
4
|
-
parent?: RenderNode | null
|
|
5
|
-
width: number
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export type ScrollHandle = {
|
|
9
|
-
scrollTop: number
|
|
10
|
-
scrollTo(options: { x: number; y: number }): void
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type ChangeFile = {
|
|
14
|
-
file: string
|
|
15
|
-
additions: number
|
|
16
|
-
deletions: number
|
|
17
|
-
status: "added" | "deleted" | "modified"
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type DiffLineKind = "hunk" | "add" | "del" | "context" | "meta"
|
|
21
|
-
export type DiffLine = { kind: DiffLineKind; text: string; line?: number }
|
|
22
|
-
|
|
23
|
-
export type DiffViewState = {
|
|
24
|
-
file: string
|
|
25
|
-
additions: number
|
|
26
|
-
deletions: number
|
|
27
|
-
lines: DiffLine[]
|
|
28
|
-
patch: string
|
|
29
|
-
message: string
|
|
30
|
-
truncated: boolean
|
|
31
|
-
loading: boolean
|
|
32
|
-
query: string
|
|
33
|
-
matchCursor: number
|
|
34
|
-
panelOpen: boolean
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export type DiffRow = {
|
|
38
|
-
kind: DiffLineKind
|
|
39
|
-
text: string
|
|
40
|
-
line: number | undefined
|
|
41
|
-
state: "current" | "hit" | "plain"
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export type FileBinds = {
|
|
45
|
-
next?: string
|
|
46
|
-
prev?: string
|
|
47
|
-
open?: string
|
|
48
|
-
stage?: string
|
|
49
|
-
unstage?: string
|
|
50
|
-
commit?: string
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export type RendererFocusEvents = {
|
|
54
|
-
on(event: "focused_renderable" | "focused_editor", listener: (...args: never[]) => void): void
|
|
55
|
-
off(event: "focused_renderable" | "focused_editor", listener: (...args: never[]) => void): void
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export type LayoutResizeEmitter = {
|
|
59
|
-
on(event: "resize", listener: () => void): void
|
|
60
|
-
off(event: "resize", listener: () => void): void
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export type Theme = Context["theme"]
|
package/src/ui/format.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { Context } from "@opencode/plugin/tui/context"
|
|
2
|
-
import type { ChangeFile, DiffLineKind } from "../types"
|
|
3
|
-
import { FILE_ROW_BADGE_WIDTH } from "../config"
|
|
4
|
-
|
|
5
|
-
type Theme = Context["theme"]
|
|
6
|
-
type ThemeColor = Theme["text"]["base"]
|
|
7
|
-
|
|
8
|
-
const ELLIPSIS = "..."
|
|
9
|
-
const runtime = globalThis as { Bun?: { stringWidth?: (text: string) => number } }
|
|
10
|
-
const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
|
11
|
-
|
|
12
|
-
export function displayWidth(text: string): number {
|
|
13
|
-
return runtime.Bun?.stringWidth?.(text) ?? [...text].length
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function truncatePath(path: string, max: number): string {
|
|
17
|
-
if (displayWidth(path) <= max) return path
|
|
18
|
-
const budget = max - ELLIPSIS.length
|
|
19
|
-
if (budget <= 0) return ELLIPSIS.slice(0, Math.max(0, max))
|
|
20
|
-
const parts = Array.from(graphemes.segment(path), (s) => s.segment)
|
|
21
|
-
let width = 0
|
|
22
|
-
let start = parts.length
|
|
23
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
24
|
-
const w = displayWidth(parts[i])
|
|
25
|
-
if (width + w > budget) break
|
|
26
|
-
width += w
|
|
27
|
-
start = i
|
|
28
|
-
}
|
|
29
|
-
return ELLIPSIS + parts.slice(start).join("")
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function maxTextWidth(available: number, reserved: number): number {
|
|
33
|
-
return Math.max(8, available - reserved)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function filePathMaxWidth(rowWidth: number, row: { additions: number; deletions: number }): number {
|
|
37
|
-
const gaps = 4
|
|
38
|
-
const fixed = FILE_ROW_BADGE_WIDTH + displayWidth(`+${row.additions}`) + displayWidth(`-${row.deletions}`)
|
|
39
|
-
return maxTextWidth(rowWidth, fixed + gaps)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function friendlyError(e: unknown): string {
|
|
43
|
-
const msg = e instanceof Error ? e.message : String(e)
|
|
44
|
-
if (/not a git repository|not a repository|no git|vcs|worktree/i.test(msg)) return "Not a git repository"
|
|
45
|
-
return msg.length > 120 ? `${msg.slice(0, 117)}…` : msg
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function statusBadge(status: ChangeFile["status"], theme: Theme): { label: string; color: ThemeColor } {
|
|
49
|
-
if (status === "added") return { label: "A", color: theme.text.feedback.success.base }
|
|
50
|
-
if (status === "deleted") return { label: "D", color: theme.text.feedback.error.base }
|
|
51
|
-
return { label: "M", color: theme.text.feedback.warning.base }
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function diffLineStyle(theme: Theme, kind: DiffLineKind): { fg: ThemeColor; bg: ThemeColor | undefined } {
|
|
55
|
-
if (kind === "hunk") return { fg: theme.diff.text.hunkHeader, bg: undefined }
|
|
56
|
-
if (kind === "add") return { fg: theme.diff.text.added, bg: theme.diff.background.added }
|
|
57
|
-
if (kind === "del") return { fg: theme.diff.text.removed, bg: theme.diff.background.removed }
|
|
58
|
-
if (kind === "meta") return { fg: theme.text.muted, bg: undefined }
|
|
59
|
-
return { fg: theme.diff.text.context, bg: theme.diff.background.context }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function diffLinePrefix(kind: DiffLineKind): string {
|
|
63
|
-
if (kind === "add") return "+"
|
|
64
|
-
if (kind === "del") return "-"
|
|
65
|
-
if (kind === "context") return " "
|
|
66
|
-
return ""
|
|
67
|
-
}
|
package/src/ui/index.ts
DELETED
package/src/ui/syntax.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
const SYNTAX_BY_LANGUAGE: Array<[string, string[]]> = [
|
|
2
|
-
["typescript", ["ts", "mts", "cts", "tsx", "mtsx", "js", "mjs", "cjs", "jsx"]],
|
|
3
|
-
["python", ["py"]],
|
|
4
|
-
["rust", ["rs"]],
|
|
5
|
-
["go", ["go"]],
|
|
6
|
-
["java", ["java"]],
|
|
7
|
-
["kotlin", ["kt", "kts"]],
|
|
8
|
-
["ruby", ["rb"]],
|
|
9
|
-
["php", ["php"]],
|
|
10
|
-
["swift", ["swift"]],
|
|
11
|
-
["c", ["c", "h"]],
|
|
12
|
-
["cpp", ["cpp", "cc", "hpp"]],
|
|
13
|
-
["csharp", ["cs"]],
|
|
14
|
-
["css", ["css"]],
|
|
15
|
-
["scss", ["scss"]],
|
|
16
|
-
["less", ["less"]],
|
|
17
|
-
["html", ["html", "htm"]],
|
|
18
|
-
["xml", ["xml"]],
|
|
19
|
-
["vue", ["vue"]],
|
|
20
|
-
["svelte", ["svelte"]],
|
|
21
|
-
["astro", ["astro"]],
|
|
22
|
-
["json", ["json"]],
|
|
23
|
-
["yaml", ["yaml", "yml"]],
|
|
24
|
-
["toml", ["toml"]],
|
|
25
|
-
["ini", ["ini"]],
|
|
26
|
-
["markdown", ["md", "markdown"]],
|
|
27
|
-
["bash", ["sh", "bash", "zsh"]],
|
|
28
|
-
["sql", ["sql"]],
|
|
29
|
-
["lua", ["lua"]],
|
|
30
|
-
["r", ["r"]],
|
|
31
|
-
["julia", ["jl"]],
|
|
32
|
-
["haskell", ["hs"]],
|
|
33
|
-
["ocaml", ["ml", "mli"]],
|
|
34
|
-
["elixir", ["ex", "exs"]],
|
|
35
|
-
["erlang", ["erl"]],
|
|
36
|
-
["clojure", ["clj", "cljs"]],
|
|
37
|
-
["scala", ["scala"]],
|
|
38
|
-
["groovy", ["groovy"]],
|
|
39
|
-
["dart", ["dart"]],
|
|
40
|
-
["zig", ["zig"]],
|
|
41
|
-
["nix", ["nix"]],
|
|
42
|
-
["terraform", ["tf"]],
|
|
43
|
-
["hcl", ["hcl"]],
|
|
44
|
-
["dockerfile", ["dockerfile"]],
|
|
45
|
-
["latex", ["tex"]],
|
|
46
|
-
["diff", ["diff", "patch"]],
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
const LANGUAGE_BY_EXTENSION: Record<string, string> = Object.fromEntries(
|
|
50
|
-
SYNTAX_BY_LANGUAGE.flatMap(([lang, exts]) => exts.map((e) => [`.${e}`, lang])),
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
export function syntaxFiletype(file: string): string | undefined {
|
|
54
|
-
const dot = file.lastIndexOf(".")
|
|
55
|
-
if (dot < 0) {
|
|
56
|
-
const base = file.split("/").pop()?.toLowerCase() ?? ""
|
|
57
|
-
if (base === "dockerfile" || base === "makefile") return base
|
|
58
|
-
return undefined
|
|
59
|
-
}
|
|
60
|
-
return LANGUAGE_BY_EXTENSION[file.slice(dot).toLowerCase()]
|
|
61
|
-
}
|
package/tui.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from "./src/tui"
|