dsh-remote-vs 0.0.0-stage → 0.8.3
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/LICENSE +21 -0
- package/README.en.md +385 -0
- package/README.md +347 -2
- package/cordis.patch.yml +259 -0
- package/icon.svg +7 -0
- package/lib/client.js +62602 -0
- package/lib/index.js +4287 -0
- package/lib/p2-bridge-refs.js +10 -0
- package/lib/p2-bridge.js +16 -0
- package/lib/p2-router-fs.js +252 -0
- package/lib/p2-router-refs.js +466 -0
- package/lib/p2-router-spill.js +62 -0
- package/lib/p2-router-subprocess.js +64 -0
- package/lib/p2-shell-bash.js +24 -0
- package/lib/ssh-proc-arI5lm4W.js +632 -0
- package/lib/types/changes/recorder.d.ts +240 -0
- package/lib/types/changes/status.d.ts +201 -0
- package/lib/types/changes/text-diff.d.ts +65 -0
- package/lib/types/client/CodeEditor.d.ts +125 -0
- package/lib/types/client/Dialog.d.ts +14 -0
- package/lib/types/client/DirectoryFlow.d.ts +30 -0
- package/lib/types/client/EditorPane.d.ts +30 -0
- package/lib/types/client/HostsView.d.ts +10 -0
- package/lib/types/client/NativeExplorer.d.ts +39 -0
- package/lib/types/client/RemoteDirectoryPicker.d.ts +16 -0
- package/lib/types/client/RemoteEditorView.d.ts +20 -0
- package/lib/types/client/RemoteExplorer.d.ts +18 -0
- package/lib/types/client/RemoteRoot.d.ts +16 -0
- package/lib/types/client/SearchView.d.ts +14 -0
- package/lib/types/client/SettingsHosts.d.ts +15 -0
- package/lib/types/client/TerminalView.d.ts +16 -0
- package/lib/types/client/WorkspaceWizard.d.ts +16 -0
- package/lib/types/client/WorkspacesView.d.ts +14 -0
- package/lib/types/client/XTerm.d.ts +15 -0
- package/lib/types/client/api.d.ts +162 -0
- package/lib/types/client/changes/ChangesDock.d.ts +35 -0
- package/lib/types/client/changes/model.d.ts +36 -0
- package/lib/types/client/changes/undo-affordance.d.ts +48 -0
- package/lib/types/client/changes/undo-context.d.ts +25 -0
- package/lib/types/client/composer.d.ts +165 -0
- package/lib/types/client/i18n.d.ts +17 -0
- package/lib/types/client/index.d.ts +14 -0
- package/lib/types/client/remote-file-open-target.d.ts +247 -0
- package/lib/types/client/remote-file-open.d.ts +35 -0
- package/lib/types/client/reveal.d.ts +15 -0
- package/lib/types/client/scm/DiffView.d.ts +24 -0
- package/lib/types/client/scm/HistoryPanel.d.ts +12 -0
- package/lib/types/client/scm/RepoPicker.d.ts +24 -0
- package/lib/types/client/scm/ScmPanel.d.ts +15 -0
- package/lib/types/client/scm/actions.d.ts +170 -0
- package/lib/types/client/scm/diff-model.d.ts +29 -0
- package/lib/types/client/scm/stage-queue.d.ts +99 -0
- package/lib/types/client/scm/status-map.d.ts +157 -0
- package/lib/types/client/scm/untracked-dirs.d.ts +72 -0
- package/lib/types/client/selection-popup.d.ts +61 -0
- package/lib/types/client/store.d.ts +437 -0
- package/lib/types/client/theme.d.ts +15 -0
- package/lib/types/client/ui.d.ts +46 -0
- package/lib/types/client/upload-plan.d.ts +78 -0
- package/lib/types/client/upload-source.d.ts +46 -0
- package/lib/types/client/use-remote-chat-target.d.ts +17 -0
- package/lib/types/client/vsc/FindWidget.d.ts +81 -0
- package/lib/types/client/vsc/change-marks.d.ts +150 -0
- package/lib/types/client/vsc/editor-langs.d.ts +5 -0
- package/lib/types/client/vsc/editor-palette.d.ts +37 -0
- package/lib/types/client/vsc/editor-theme.d.ts +32 -0
- package/lib/types/client/vsc/fallback-highlight.d.ts +26 -0
- package/lib/types/client/vsc/kit.d.ts +195 -0
- package/lib/types/client/vsc/markdown.d.ts +11 -0
- package/lib/types/client/vsc/reveal-line.d.ts +38 -0
- package/lib/types/client/vsc/tokenize.d.ts +104 -0
- package/lib/types/deploy.d.ts +94 -0
- package/lib/types/fs-ops.d.ts +8 -0
- package/lib/types/git-format.d.ts +34 -0
- package/lib/types/git-ops.d.ts +183 -0
- package/lib/types/http.d.ts +14 -0
- package/lib/types/importers.d.ts +20 -0
- package/lib/types/index.d.ts +87 -0
- package/lib/types/keygen.d.ts +19 -0
- package/lib/types/loopback.d.ts +7 -0
- package/lib/types/p2/bridge-refs.d.ts +21 -0
- package/lib/types/p2/bridge.d.ts +24 -0
- package/lib/types/p2/file-refs.d.ts +73 -0
- package/lib/types/p2/helper-rpc.d.ts +63 -0
- package/lib/types/p2/in-flight-gate.d.ts +25 -0
- package/lib/types/p2/router-fs.d.ts +92 -0
- package/lib/types/p2/router-refs.d.ts +45 -0
- package/lib/types/p2/router-spill.d.ts +11 -0
- package/lib/types/p2/router-subprocess.d.ts +23 -0
- package/lib/types/p2/shell-bash.d.ts +16 -0
- package/lib/types/p2/ssh-proc.d.ts +22 -0
- package/lib/types/p2/worlds.d.ts +179 -0
- package/lib/types/paths.d.ts +12 -0
- package/lib/types/pool.d.ts +105 -0
- package/lib/types/protocol.d.ts +452 -0
- package/lib/types/routes.d.ts +27 -0
- package/lib/types/search-ops.d.ts +68 -0
- package/lib/types/sftp.d.ts +90 -0
- package/lib/types/store.d.ts +66 -0
- package/lib/types/terminal.d.ts +51 -0
- package/lib/types/timeouts.d.ts +83 -0
- package/lib/types/tools.d.ts +41 -0
- package/lib/worlds-PUumRIh7.js +18765 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +189 -4
- package/src/changes/recorder.ts +902 -0
- package/src/changes/status.ts +448 -0
- package/src/changes/text-diff.ts +377 -0
- package/src/client/CodeEditor.tsx +923 -0
- package/src/client/Dialog.tsx +76 -0
- package/src/client/DirectoryFlow.tsx +431 -0
- package/src/client/EditorPane.tsx +512 -0
- package/src/client/HostsView.tsx +312 -0
- package/src/client/NativeExplorer.tsx +121 -0
- package/src/client/RemoteDirectoryPicker.tsx +157 -0
- package/src/client/RemoteEditorView.tsx +54 -0
- package/src/client/RemoteExplorer.tsx +1398 -0
- package/src/client/RemoteRoot.tsx +389 -0
- package/src/client/SearchView.tsx +407 -0
- package/src/client/SettingsHosts.tsx +136 -0
- package/src/client/TerminalView.tsx +308 -0
- package/src/client/WorkspaceWizard.tsx +199 -0
- package/src/client/WorkspacesView.tsx +93 -0
- package/src/client/XTerm.tsx +308 -0
- package/src/client/api.ts +331 -0
- package/src/client/changes/ChangesDock.tsx +348 -0
- package/src/client/changes/changes.css +230 -0
- package/src/client/changes/model.ts +347 -0
- package/src/client/changes/undo-affordance.ts +389 -0
- package/src/client/changes/undo-context.ts +34 -0
- package/src/client/composer.ts +436 -0
- package/src/client/css.d.ts +7 -0
- package/src/client/i18n.ts +702 -0
- package/src/client/index.tsx +286 -0
- package/src/client/remote-file-open-target.ts +553 -0
- package/src/client/remote-file-open.ts +261 -0
- package/src/client/reveal.ts +33 -0
- package/src/client/scm/DiffView.tsx +570 -0
- package/src/client/scm/HistoryPanel.tsx +353 -0
- package/src/client/scm/RepoPicker.tsx +238 -0
- package/src/client/scm/ScmPanel.tsx +857 -0
- package/src/client/scm/actions.ts +460 -0
- package/src/client/scm/diff-model.ts +206 -0
- package/src/client/scm/stage-queue.ts +264 -0
- package/src/client/scm/status-map.ts +381 -0
- package/src/client/scm/untracked-dirs.ts +139 -0
- package/src/client/selection-popup.ts +157 -0
- package/src/client/store.tsx +1670 -0
- package/src/client/theme.ts +54 -0
- package/src/client/ui.tsx +136 -0
- package/src/client/upload-plan.ts +153 -0
- package/src/client/upload-source.ts +145 -0
- package/src/client/use-remote-chat-target.ts +153 -0
- package/src/client/vsc/FindWidget.tsx +446 -0
- package/src/client/vsc/change-marks.ts +586 -0
- package/src/client/vsc/editor-langs.ts +137 -0
- package/src/client/vsc/editor-palette.ts +107 -0
- package/src/client/vsc/editor-theme.ts +93 -0
- package/src/client/vsc/fallback-highlight.ts +145 -0
- package/src/client/vsc/kit.tsx +777 -0
- package/src/client/vsc/markdown.tsx +172 -0
- package/src/client/vsc/reveal-line.ts +142 -0
- package/src/client/vsc/tokenize.ts +569 -0
- package/src/client/vsc/vsc.css +2669 -0
- package/src/deploy.ts +481 -0
- package/src/fs-ops.ts +57 -0
- package/src/git-format.ts +205 -0
- package/src/git-ops.ts +693 -0
- package/src/http.ts +55 -0
- package/src/importers.ts +141 -0
- package/src/index.ts +442 -0
- package/src/keygen.ts +77 -0
- package/src/loopback.ts +47 -0
- package/src/p2/bridge-refs.ts +27 -0
- package/src/p2/bridge.ts +32 -0
- package/src/p2/file-refs.ts +392 -0
- package/src/p2/helper-rpc.ts +394 -0
- package/src/p2/in-flight-gate.ts +63 -0
- package/src/p2/router-fs.ts +327 -0
- package/src/p2/router-refs.ts +166 -0
- package/src/p2/router-spill.ts +75 -0
- package/src/p2/router-subprocess.ts +92 -0
- package/src/p2/shell-bash.ts +30 -0
- package/src/p2/ssh-proc.ts +802 -0
- package/src/p2/worlds.ts +735 -0
- package/src/paths.ts +50 -0
- package/src/pool.ts +811 -0
- package/src/protocol.ts +460 -0
- package/src/routes.ts +802 -0
- package/src/search-ops.ts +220 -0
- package/src/sftp.ts +677 -0
- package/src/store.ts +364 -0
- package/src/terminal.ts +291 -0
- package/src/timeouts.ts +122 -0
- package/src/tools.ts +320 -0
- package/vendor/dsh-remote-helpers/dsh-ssh-helper-0.2.1-alpha.2-linux-x64.helper.json +16 -0
- package/vendor/dsh-remote-helpers/dsh-ssh-helper-0.2.1-alpha.2-linux-x64.tar.gz +4 -0
- package/vendor/dsh-remote-helpers/dsh-ssh-helper-0.2.1-alpha.2-linux-x64.tar.gz.sha256 +1 -0
- package/vendor/dsh-remote-helpers/glibc-ceiling.patch +31 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, dependency-free Markdown renderer for the remote preview pane.
|
|
3
|
+
*
|
|
4
|
+
* It renders React elements directly (never `dangerouslySetInnerHTML`), so raw
|
|
5
|
+
* HTML in a remote document stays inert text. Coverage is the subset that
|
|
6
|
+
* matters when reading a repository file: headings, fenced and inline code,
|
|
7
|
+
* lists, blockquotes, rules, GFM tables, links, bold / italic / strike.
|
|
8
|
+
*/
|
|
9
|
+
import { createElement, type ReactNode } from 'react'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Schemes a remote document's link may use.
|
|
13
|
+
*
|
|
14
|
+
* The preview renders UNTRUSTED content: any repository the user browses (or
|
|
15
|
+
* a file an agent wrote) can contain `[x](javascript:...)`, which matches the
|
|
16
|
+
* inline pattern below unchanged. React 18 only warns about a `javascript:`
|
|
17
|
+
* href — it does not strip the attribute — and such a URL executes in the
|
|
18
|
+
* current document regardless of `target`, i.e. in the DSH GUI's own origin.
|
|
19
|
+
* Anything not matching is rendered as plain text instead of a link.
|
|
20
|
+
*/
|
|
21
|
+
const SAFE_HREF = /^(?:https?:|mailto:|#|\/(?!\/)|\.{1,2}\/)/i
|
|
22
|
+
|
|
23
|
+
/** Inline formatting: code spans, links, bold, italic, strike. */
|
|
24
|
+
function inline(text: string, keyBase: string): ReactNode[] {
|
|
25
|
+
const nodes: ReactNode[] = []
|
|
26
|
+
const pattern = /(`[^`]+`)|(\*\*[^*]+\*\*)|(__[^_]+__)|(\*[^*\n]+\*)|(_[^_\n]+_)|(~~[^~]+~~)|(\[[^\]]+\]\([^)\s]+\))/g
|
|
27
|
+
let last = 0
|
|
28
|
+
let index = 0
|
|
29
|
+
for (let match = pattern.exec(text); match !== null; match = pattern.exec(text)) {
|
|
30
|
+
if (match.index > last) nodes.push(text.slice(last, match.index))
|
|
31
|
+
const token = match[0]
|
|
32
|
+
const key = `${keyBase}-i${index++}`
|
|
33
|
+
if (token.startsWith('`')) {
|
|
34
|
+
nodes.push(createElement('code', { key }, token.slice(1, -1)))
|
|
35
|
+
} else if (token.startsWith('**') || token.startsWith('__')) {
|
|
36
|
+
nodes.push(createElement('strong', { key }, token.slice(2, -2)))
|
|
37
|
+
} else if (token.startsWith('~~')) {
|
|
38
|
+
nodes.push(createElement('del', { key }, token.slice(2, -2)))
|
|
39
|
+
} else if (token.startsWith('[')) {
|
|
40
|
+
const split = token.indexOf('](')
|
|
41
|
+
const label = token.slice(1, split)
|
|
42
|
+
const href = token.slice(split + 2, -1)
|
|
43
|
+
if (SAFE_HREF.test(href)) {
|
|
44
|
+
nodes.push(createElement('a', { key, href, target: '_blank', rel: 'noreferrer noopener' }, label))
|
|
45
|
+
} else {
|
|
46
|
+
// Unsafe scheme (javascript:, data:, vbscript:, …): keep the text, drop
|
|
47
|
+
// the link. Skipping the token inside a single createElement pair keeps
|
|
48
|
+
// the label readable but inert.
|
|
49
|
+
nodes.push(createElement('span', { key, title: href }, label))
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
nodes.push(createElement('em', { key }, token.slice(1, -1)))
|
|
53
|
+
}
|
|
54
|
+
last = match.index + token.length
|
|
55
|
+
}
|
|
56
|
+
if (last < text.length) nodes.push(text.slice(last))
|
|
57
|
+
return nodes
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Split one table row into cells. */
|
|
61
|
+
function cells(line: string): string[] {
|
|
62
|
+
return line.replace(/^\s*\|/, '').replace(/\|\s*$/, '').split('|').map(cell => cell.trim())
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Render a whole document. */
|
|
66
|
+
export function renderMarkdown(text: string): ReactNode {
|
|
67
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n')
|
|
68
|
+
const out: ReactNode[] = []
|
|
69
|
+
let i = 0
|
|
70
|
+
let key = 0
|
|
71
|
+
const nextKey = (): string => `md-${key++}`
|
|
72
|
+
|
|
73
|
+
while (i < lines.length) {
|
|
74
|
+
const line = lines[i] as string
|
|
75
|
+
|
|
76
|
+
// Fenced code.
|
|
77
|
+
const fence = /^\s*(```|~~~)\s*([\w+-]*)\s*$/.exec(line)
|
|
78
|
+
if (fence !== null) {
|
|
79
|
+
const marker = fence[1] as string
|
|
80
|
+
const body: string[] = []
|
|
81
|
+
i += 1
|
|
82
|
+
while (i < lines.length && !(lines[i] as string).trimStart().startsWith(marker)) {
|
|
83
|
+
body.push(lines[i] as string)
|
|
84
|
+
i += 1
|
|
85
|
+
}
|
|
86
|
+
i += 1
|
|
87
|
+
out.push(createElement('pre', { key: nextKey() }, createElement('code', null, body.join('\n'))))
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Heading.
|
|
92
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line)
|
|
93
|
+
if (heading !== null) {
|
|
94
|
+
const level = Math.min(6, (heading[1] as string).length)
|
|
95
|
+
out.push(createElement(`h${level}`, { key: nextKey() }, inline(heading[2] as string, nextKey())))
|
|
96
|
+
i += 1
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Horizontal rule.
|
|
101
|
+
if (/^\s*([-*_])\s*\1\s*\1[\s\S]*$/.test(line) && line.replace(/[\s\-*_]/g, '') === '') {
|
|
102
|
+
out.push(createElement('hr', { key: nextKey() }))
|
|
103
|
+
i += 1
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Table (header row + separator).
|
|
108
|
+
if (line.includes('|') && i + 1 < lines.length && /^\s*\|?[\s:|-]+\|[\s:|-]*$/.test(lines[i + 1] as string)) {
|
|
109
|
+
const head = cells(line)
|
|
110
|
+
i += 2
|
|
111
|
+
const rows: string[][] = []
|
|
112
|
+
while (i < lines.length && (lines[i] as string).includes('|') && (lines[i] as string).trim() !== '') {
|
|
113
|
+
rows.push(cells(lines[i] as string))
|
|
114
|
+
i += 1
|
|
115
|
+
}
|
|
116
|
+
out.push(createElement('table', { key: nextKey() },
|
|
117
|
+
createElement('thead', null,
|
|
118
|
+
createElement('tr', null, head.map((cell, index) => createElement('th', { key: index }, inline(cell, nextKey()))))),
|
|
119
|
+
createElement('tbody', null, rows.map((row, rowIndex) => createElement('tr', { key: rowIndex },
|
|
120
|
+
row.map((cell, cellIndex) => createElement('td', { key: cellIndex }, inline(cell, nextKey()))))))))
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Blockquote.
|
|
125
|
+
if (/^\s*>/.test(line)) {
|
|
126
|
+
const body: string[] = []
|
|
127
|
+
while (i < lines.length && /^\s*>/.test(lines[i] as string)) {
|
|
128
|
+
body.push((lines[i] as string).replace(/^\s*>\s?/, ''))
|
|
129
|
+
i += 1
|
|
130
|
+
}
|
|
131
|
+
out.push(createElement('blockquote', { key: nextKey() }, renderMarkdown(body.join('\n'))))
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Lists.
|
|
136
|
+
const listMatch = /^(\s*)([-*+]|\d+[.)])\s+(.*)$/.exec(line)
|
|
137
|
+
if (listMatch !== null) {
|
|
138
|
+
const ordered = /^\d/.test(listMatch[2] as string)
|
|
139
|
+
const items: string[] = []
|
|
140
|
+
while (i < lines.length) {
|
|
141
|
+
const current = /^(\s*)([-*+]|\d+[.)])\s+(.*)$/.exec(lines[i] as string)
|
|
142
|
+
if (current === null) break
|
|
143
|
+
items.push(current[3] as string)
|
|
144
|
+
i += 1
|
|
145
|
+
}
|
|
146
|
+
out.push(createElement(ordered ? 'ol' : 'ul', { key: nextKey() },
|
|
147
|
+
items.map((item, index) => createElement('li', { key: index }, inline(item, nextKey())))))
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Blank line.
|
|
152
|
+
if (line.trim() === '') {
|
|
153
|
+
i += 1
|
|
154
|
+
continue
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Paragraph: consume until a blank line or another block starter.
|
|
158
|
+
const paragraph: string[] = [line]
|
|
159
|
+
i += 1
|
|
160
|
+
while (i < lines.length) {
|
|
161
|
+
const current = lines[i] as string
|
|
162
|
+
if (current.trim() === '') break
|
|
163
|
+
if (/^(#{1,6})\s/.test(current) || /^\s*(```|~~~)/.test(current) || /^\s*>/.test(current)) break
|
|
164
|
+
if (/^(\s*)([-*+]|\d+[.)])\s+/.test(current)) break
|
|
165
|
+
paragraph.push(current)
|
|
166
|
+
i += 1
|
|
167
|
+
}
|
|
168
|
+
out.push(createElement('p', { key: nextKey() }, inline(paragraph.join(' '), nextKey())))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return createElement('div', { className: 'rvs-markdown' }, out)
|
|
172
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Land here": the jump a search hit (or an `@`-reference) performs.
|
|
3
|
+
*
|
|
4
|
+
* Kept as one module-level helper because it has to work on an editor that is
|
|
5
|
+
* ALREADY mounted — clicking a second hit in the same file must re-jump, not
|
|
6
|
+
* silently do nothing (which is what a mount-only reveal does).
|
|
7
|
+
*/
|
|
8
|
+
import { EditorSelection, StateEffect, StateField } from '@codemirror/state'
|
|
9
|
+
import { Decoration, EditorView, type DecorationSet } from '@codemirror/view'
|
|
10
|
+
|
|
11
|
+
/** Set (or clear) the landing highlight on one line. */
|
|
12
|
+
export const setRevealLine = StateEffect.define<number | null>()
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The landing highlight for a jump (search hit, `@` reference): without it a
|
|
16
|
+
* jump into a long file leaves the caret in a wall of identical lines and the
|
|
17
|
+
* user has to hunt for it.
|
|
18
|
+
*/
|
|
19
|
+
export const revealLineField = StateField.define<DecorationSet>({
|
|
20
|
+
create: () => Decoration.none,
|
|
21
|
+
update(value, tr) {
|
|
22
|
+
let next = value.map(tr.changes)
|
|
23
|
+
for (const effect of tr.effects) {
|
|
24
|
+
if (!effect.is(setRevealLine)) continue
|
|
25
|
+
next = effect.value === null
|
|
26
|
+
? Decoration.none
|
|
27
|
+
: Decoration.set([Decoration.line({ class: 'rvs-reveal-line' }).range(effect.value)])
|
|
28
|
+
}
|
|
29
|
+
return next
|
|
30
|
+
},
|
|
31
|
+
provide: field => EditorView.decorations.from(field),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/** How long the landing highlight stays lit. */
|
|
35
|
+
const LIT_MS = 2600
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Scroll to a 1-based line, put the caret on it and light it up.
|
|
39
|
+
*
|
|
40
|
+
* Centering is done in TWO steps on purpose: CodeMirror scrolls its own
|
|
41
|
+
* scroller, but the editor can also sit inside a taller ancestor scroller (the
|
|
42
|
+
* panel height is not ours to control), and only `scrollIntoView` walks every
|
|
43
|
+
* scrollable ancestor. Without the second step the line can land off-screen.
|
|
44
|
+
*/
|
|
45
|
+
export function revealLine(view: EditorView, target: number, lit: (position: number | null) => void): void {
|
|
46
|
+
let line = 0
|
|
47
|
+
try {
|
|
48
|
+
const clamped = Math.min(Math.max(1, Math.round(target)), view.state.doc.lines)
|
|
49
|
+
line = view.state.doc.line(clamped).number
|
|
50
|
+
const from = view.state.doc.line(line).from
|
|
51
|
+
view.dispatch({
|
|
52
|
+
selection: { anchor: from },
|
|
53
|
+
effects: EditorView.scrollIntoView(from, { y: 'center' }),
|
|
54
|
+
})
|
|
55
|
+
lit(from)
|
|
56
|
+
} catch {
|
|
57
|
+
// A stale line number or a disposed view: nothing to reveal.
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
view.focus()
|
|
61
|
+
// Second step: centre the line in the *visible* area whatever scrolls.
|
|
62
|
+
window.setTimeout(() => {
|
|
63
|
+
try {
|
|
64
|
+
const element = lineElement(view, line)
|
|
65
|
+
element?.scrollIntoView({ block: 'center', inline: 'nearest' })
|
|
66
|
+
} catch {
|
|
67
|
+
// No layout: the CodeMirror scroll already did its best.
|
|
68
|
+
}
|
|
69
|
+
}, 30)
|
|
70
|
+
window.setTimeout(() => {
|
|
71
|
+
try {
|
|
72
|
+
lit(null)
|
|
73
|
+
} catch {
|
|
74
|
+
// Disposed.
|
|
75
|
+
}
|
|
76
|
+
}, LIT_MS)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The rendered `.cm-line` element for a 1-based line number, if visible. */
|
|
80
|
+
function lineElement(view: EditorView, line: number): HTMLElement | null {
|
|
81
|
+
const position = view.state.doc.line(line).from
|
|
82
|
+
if (position < view.viewport.from || position > view.viewport.to) return null
|
|
83
|
+
const dom = view.domAtPos(position)
|
|
84
|
+
let node: Node | null = dom.node
|
|
85
|
+
while (node !== null && node !== view.dom) {
|
|
86
|
+
if (node instanceof HTMLElement && node.classList.contains('cm-line')) return node
|
|
87
|
+
node = node.parentNode
|
|
88
|
+
}
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 选中一个行区间并把它带到视野中央(「上一个/下一个改动点」用)。
|
|
94
|
+
*
|
|
95
|
+
* 与 `revealLine` 的差别只有两点:用 `EditorSelection.range` 把整段选起来(一个
|
|
96
|
+
* 改动块常常有好几行,只放一个 caret 看不出这次改动有多大),以及**不**点灯 —
|
|
97
|
+
* 改动块本身已经有自己的绿/红背景,再叠一层 landing 蓝只会糊成第三种颜色。
|
|
98
|
+
*
|
|
99
|
+
* `from` / `to` 都是 **1-based 行号**(与 `revealLine` 同一套语义),越界夹到文件
|
|
100
|
+
* 首尾,`from > to` 时自动对调。`lit` 与 `revealLine` 同义,给那些需要额外提示
|
|
101
|
+
* 的调用方留口子(传 null 收灯,`LIT_MS` 后自动熄灭)。
|
|
102
|
+
*/
|
|
103
|
+
export function revealRange(
|
|
104
|
+
view: EditorView,
|
|
105
|
+
from: number,
|
|
106
|
+
to: number,
|
|
107
|
+
lit?: (position: number | null) => void,
|
|
108
|
+
): void {
|
|
109
|
+
let line = 0
|
|
110
|
+
try {
|
|
111
|
+
const total = view.state.doc.lines
|
|
112
|
+
const firstNumber = Math.min(Math.max(1, Math.round(Math.min(from, to))), total)
|
|
113
|
+
const lastNumber = Math.min(Math.max(firstNumber, Math.round(Math.max(from, to))), total)
|
|
114
|
+
const first = view.state.doc.line(firstNumber)
|
|
115
|
+
const last = view.state.doc.line(lastNumber)
|
|
116
|
+
line = firstNumber
|
|
117
|
+
view.dispatch({
|
|
118
|
+
selection: EditorSelection.range(first.from, last.to),
|
|
119
|
+
effects: EditorView.scrollIntoView(first.from, { y: 'center' }),
|
|
120
|
+
})
|
|
121
|
+
lit?.(first.from)
|
|
122
|
+
} catch {
|
|
123
|
+
// A stale line number or a disposed view: nothing to reveal.
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
view.focus()
|
|
127
|
+
// 与 revealLine 同理:祖先滚动容器也要走一遍,否则选中的块可能整个在视野外。
|
|
128
|
+
window.setTimeout(() => {
|
|
129
|
+
try {
|
|
130
|
+
lineElement(view, line)?.scrollIntoView({ block: 'center', inline: 'nearest' })
|
|
131
|
+
} catch {
|
|
132
|
+
// No layout: the CodeMirror scroll already did its best.
|
|
133
|
+
}
|
|
134
|
+
}, 30)
|
|
135
|
+
window.setTimeout(() => {
|
|
136
|
+
try {
|
|
137
|
+
lit?.(null)
|
|
138
|
+
} catch {
|
|
139
|
+
// Disposed.
|
|
140
|
+
}
|
|
141
|
+
}, LIT_MS)
|
|
142
|
+
}
|