coc-vscode-loader 1.2.6 → 1.2.7
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 -6
- package/assets/tui-preview.png +0 -0
- package/converter/package-lock.json +2 -2
- package/converter/package.json +1 -1
- package/converter/src/scanner.ts +1 -1
- package/converter/src/steps/language-client.ts +14 -10
- package/converter/src/steps/snippets.ts +21 -0
- package/converter/src/transforms/class-to-factory.ts +3 -5
- package/converter/src/transforms/provider-register.ts +30 -8
- package/converter/src/types.ts +2 -0
- package/lib/index.js +420 -173
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,10 +37,10 @@ npm install coc-vscode-loader
|
|
|
37
37
|
| `x` | Toggle mark package for batch operations |
|
|
38
38
|
| `f` | Cycle filter: all → installed → available |
|
|
39
39
|
| `s` | Cycle sort: default → name → status → type |
|
|
40
|
-
| `
|
|
41
|
-
| `gg` | Jump to first
|
|
42
|
-
| `G` | Jump to last
|
|
43
|
-
| `<CR>` |
|
|
40
|
+
| `j` / `k` | Scroll through packages (virtual scroll) |
|
|
41
|
+
| `gg` | Jump to first package |
|
|
42
|
+
| `G` | Jump to last package |
|
|
43
|
+
| `<CR>` | Open detail popup (info / install log with syntax highlights) |
|
|
44
44
|
| `/` | Search filter |
|
|
45
45
|
| `q` | Close (auto `:CocRestart` if changes detected) |
|
|
46
46
|
| `<Esc>` | Help→Search→Clear marks→Cancel|Close |
|
|
@@ -61,13 +61,13 @@ npm install coc-vscode-loader
|
|
|
61
61
|
|
|
62
62
|
- **Real conversion pipeline** — git clone → converter → npm install → esbuild → register to coc
|
|
63
63
|
- **Auto-fetch registry** — remote registry fetched in background when TUI opens, no manual refresh needed
|
|
64
|
-
- **
|
|
64
|
+
- **Virtual scrolling** — `j`/`k` smooth scroll through packages, handles 100k+ registry entries
|
|
65
65
|
- **Incremental cache** — source/ keeps git repo, updates via git pull only
|
|
66
66
|
- **Commit tracking** — records commit SHA after install, visible in detail view
|
|
67
67
|
- **Update check** — `C` key compares against remote HEAD, shows `↑` when outdated
|
|
68
68
|
- **Auto restart** — `:CocRestart` triggered automatically on close when changes detected
|
|
69
69
|
- **Manual registry update** — `:CocCommand loader.updateRegistry` also available for re-fetch
|
|
70
|
-
- **
|
|
70
|
+
- **Detail popup** — `<CR>` opens centered float window with package info or live install log (syntax highlighted, auto-scroll to latest)
|
|
71
71
|
- **Mark & batch** — `x` toggle mark, visual indicator, `D` clean orphaned packages
|
|
72
72
|
- **Filter & sort** — `f` cycle view filter, `s` cycle sort order (name/status/type)
|
|
73
73
|
- **Concurrency limit** — max 3 parallel operations for `U` (Update All)
|
package/assets/tui-preview.png
CHANGED
|
Binary file
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "converter",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "converter",
|
|
9
|
-
"version": "1.2.
|
|
9
|
+
"version": "1.2.7",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"commander": "^15.0.0",
|
|
12
12
|
"ts-morph": "^28.0.0",
|
package/converter/package.json
CHANGED
package/converter/src/scanner.ts
CHANGED
|
@@ -23,7 +23,7 @@ export function scan(dir: string): ScanResult {
|
|
|
23
23
|
const apis: string[] = []
|
|
24
24
|
const relative = path.relative(dir, filePath)
|
|
25
25
|
|
|
26
|
-
if (content.includes("from 'vscode'") || content.includes('from "vscode"') || content.includes('require("vscode")')) {
|
|
26
|
+
if (content.includes("from 'vscode'") || content.includes('from "vscode"') || content.includes('require("vscode")') || content.includes("require('vscode')")) {
|
|
27
27
|
apis.push('vscode')
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -108,7 +108,7 @@ ${ls.verbose ? ` console.log('[${escapeStr(id)}] creating LanguageClient')\n`
|
|
|
108
108
|
{
|
|
109
109
|
documentSelector: ${docSelectorCode},
|
|
110
110
|
outputChannelName: '${escapeStr(description)}',
|
|
111
|
-
${ls.initializationOptions ? `initializationOptions: ${ls.initializationOptions},` : ''}
|
|
111
|
+
${ls.initializationOptions ? `initializationOptions: ${ls.initializationOptions.replace(/`/g, '\\`').replace(/\$\{/g, '\\${')},` : ''}
|
|
112
112
|
},
|
|
113
113
|
)
|
|
114
114
|
context.subscriptions.push({ dispose: () => c.stop() })
|
|
@@ -117,23 +117,27 @@ ${ls.verbose ? ` console.log('[${escapeStr(id)}] creating LanguageClient')\n`
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
${ls.verbose ? ` console.log('[${escapeStr(id)}] registering LanguageClient')\n` : ''}\
|
|
120
|
-
let
|
|
120
|
+
let clients: LanguageClient[]
|
|
121
121
|
if (${multiRoot ? 'workspace.workspaceFolders && workspace.workspaceFolders.length > 1' : 'false'}) {
|
|
122
122
|
${ls.verbose ? ` console.log('[${escapeStr(id)}] multiRoot mode')\n` : ''}\
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
clients = workspace.workspaceFolders.map(folder => {
|
|
124
|
+
const c = createClient()
|
|
125
|
+
c.start()
|
|
126
|
+
return c
|
|
127
|
+
})
|
|
127
128
|
} else {
|
|
128
|
-
|
|
129
|
+
const c = createClient()
|
|
129
130
|
${ls.verbose ? ` console.log('[${escapeStr(id)}] starting client')\n` : ''}\
|
|
130
|
-
|
|
131
|
+
c.start()
|
|
132
|
+
clients = [c]
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
context.subscriptions.push(
|
|
134
136
|
commands.registerCommand('${escapeStr(pluginName)}.restart', async () => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
for (const c of clients) {
|
|
138
|
+
await c.stop()
|
|
139
|
+
await c.start()
|
|
140
|
+
}
|
|
137
141
|
}),
|
|
138
142
|
)
|
|
139
143
|
} catch (e: any) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs'
|
|
2
2
|
import * as path from 'path'
|
|
3
|
+
import { execFileSync } from 'child_process'
|
|
3
4
|
import { StepGenerator, StepContext, SnippetsStep, StepResult } from '../types.js'
|
|
4
5
|
|
|
5
6
|
export const snippetsGenerator: StepGenerator = {
|
|
@@ -71,6 +72,26 @@ export const snippetsGenerator: StepGenerator = {
|
|
|
71
72
|
if (verbose) console.log(` snippets: copied ${sourceRelPath} (${languages.join(', ')})`)
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
if (copiedCount === 0 && ss.build) {
|
|
76
|
+
// Run build script to generate snippet files (e.g. node merge.js)
|
|
77
|
+
if (verbose) console.log(` snippets: running build: ${ss.build}`)
|
|
78
|
+
execFileSync('npm', ['install', '--legacy-peer-deps'], { cwd: input, stdio: verbose ? 'inherit' : 'pipe' })
|
|
79
|
+
const [cmd, ...args] = ss.build.split(' ')
|
|
80
|
+
execFileSync(cmd, args, { cwd: input, stdio: verbose ? 'inherit' : 'pipe' })
|
|
81
|
+
// Retry copying
|
|
82
|
+
for (const [sourceRelPath, languages] of fileToLanguages) {
|
|
83
|
+
const sourceFile = path.join(input, sourceRelPath)
|
|
84
|
+
if (fs.existsSync(sourceFile)) {
|
|
85
|
+
const dest = path.join(output, sourceRelPath)
|
|
86
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
87
|
+
fs.copyFileSync(sourceFile, dest)
|
|
88
|
+
copiedCount++
|
|
89
|
+
allLanguages.push(...languages)
|
|
90
|
+
if (verbose) console.log(` snippets: copied ${sourceRelPath} (${languages.join(', ')})`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
74
95
|
if (copiedCount === 0) {
|
|
75
96
|
throw new Error('snippets step: no snippet files were copied')
|
|
76
97
|
}
|
|
@@ -20,16 +20,14 @@ export const transformClassToFactory: Transform = (ctx) => {
|
|
|
20
20
|
|
|
21
21
|
// AST approach: try to replace via ts-morph
|
|
22
22
|
const nodes = file.getDescendantsOfKind(SyntaxKind.NewExpression)
|
|
23
|
-
|
|
23
|
+
// Sort by position descending so inner nodes are processed first
|
|
24
|
+
nodes.sort((a, b) => b.getPos() - a.getPos())
|
|
24
25
|
for (const expr of nodes) {
|
|
25
26
|
const text = expr.getText()
|
|
26
27
|
const m = text.match(/^new\s+(\w+)\(/)
|
|
27
28
|
if (!m || !FACTORY_TYPES.has(m[1])) continue
|
|
28
29
|
const args = text.slice(m[0].length, -1)
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
for (const { node, text } of astReplacements) {
|
|
32
|
-
try { node.replaceWithText(text) } catch {}
|
|
30
|
+
try { expr.replaceWithText(`${m[1]}.create(${args})`) } catch {}
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
// Text fallback: catch remaining new Xxx() that AST might have missed
|
|
@@ -44,14 +44,36 @@ export const transformProviderRegister: Transform = (ctx) => {
|
|
|
44
44
|
`registerCompletionItemProvider('${pluginName}', '${shortcut}', `
|
|
45
45
|
)
|
|
46
46
|
// Wrap the last argument in an array if it's a string (trigger chars)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
// Use paren-balancing to handle nested parentheses in arguments
|
|
48
|
+
// Build result by iterating over all matches, replacing each full call
|
|
49
|
+
const providerRe = /registerCompletionItemProvider\(/g
|
|
50
|
+
let result = ''
|
|
51
|
+
let lastIdx = 0
|
|
52
|
+
let m: RegExpExecArray | null
|
|
53
|
+
while ((m = providerRe.exec(content)) !== null) {
|
|
54
|
+
const start = m.index
|
|
55
|
+
let depth = 1
|
|
56
|
+
let i = start + m[0].length
|
|
57
|
+
while (i < content.length && depth > 0) {
|
|
58
|
+
if (content[i] === '(') depth++
|
|
59
|
+
else if (content[i] === ')') depth--
|
|
60
|
+
i++
|
|
61
|
+
}
|
|
62
|
+
const end = i
|
|
63
|
+
const fullCall = content.slice(start, end)
|
|
64
|
+
const lastStrMatch = fullCall.match(/,?\s*'([^']+)'\s*\)$/)
|
|
65
|
+
const lastDblMatch = fullCall.match(/,?\s*"([^"]+)"\s*\)$/)
|
|
66
|
+
let replacement = fullCall
|
|
67
|
+
if (lastStrMatch) {
|
|
68
|
+
replacement = fullCall.slice(0, fullCall.length - lastStrMatch[0].length) + ', ["' + lastStrMatch[1] + '"])'
|
|
69
|
+
} else if (lastDblMatch) {
|
|
70
|
+
replacement = fullCall.slice(0, fullCall.length - lastDblMatch[0].length) + ', ["' + lastDblMatch[1] + '"])'
|
|
71
|
+
}
|
|
72
|
+
result += content.slice(lastIdx, start) + replacement
|
|
73
|
+
lastIdx = end
|
|
74
|
+
}
|
|
75
|
+
result += content.slice(lastIdx)
|
|
76
|
+
content = result
|
|
55
77
|
changed = true
|
|
56
78
|
}
|
|
57
79
|
|
package/converter/src/types.ts
CHANGED
|
@@ -77,6 +77,8 @@ export interface SnippetsStep {
|
|
|
77
77
|
type: 'snippets'
|
|
78
78
|
/** Optional: override languages to generate (default: read from source package.json's contributes.snippets) */
|
|
79
79
|
languages?: string[]
|
|
80
|
+
/** Optional: build command to run in source dir before collecting snippet files (e.g. "node merge.js") */
|
|
81
|
+
build?: string
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
export type ConvertStep = LanguageClientStep | SourceStep | BridgeStep | MarkUnsupportedStep | SnippetsStep
|
package/lib/index.js
CHANGED
|
@@ -39,7 +39,7 @@ var require_package = __commonJS({
|
|
|
39
39
|
"package.json"(exports2, module2) {
|
|
40
40
|
module2.exports = {
|
|
41
41
|
name: "coc-vscode-loader",
|
|
42
|
-
version: "1.2.
|
|
42
|
+
version: "1.2.7",
|
|
43
43
|
description: "Run VS Code extensions seamlessly in coc.nvim",
|
|
44
44
|
main: "lib/index.js",
|
|
45
45
|
keywords: [
|
|
@@ -167,22 +167,47 @@ function loadCache() {
|
|
|
167
167
|
}
|
|
168
168
|
return null;
|
|
169
169
|
}
|
|
170
|
-
async function fetchRegistryJSON(url) {
|
|
170
|
+
async function fetchRegistryJSON(url, onProgress) {
|
|
171
171
|
try {
|
|
172
172
|
const ctrl = new AbortController();
|
|
173
173
|
const t = setTimeout(() => ctrl.abort(), 1e4);
|
|
174
174
|
const res = await fetch(url, { signal: ctrl.signal });
|
|
175
175
|
clearTimeout(t);
|
|
176
176
|
if (res.ok) {
|
|
177
|
-
const
|
|
177
|
+
const total = parseInt(res.headers.get("content-length") || "0");
|
|
178
|
+
const reader = res.body.getReader();
|
|
179
|
+
const chunks = [];
|
|
180
|
+
let received = 0;
|
|
181
|
+
while (true) {
|
|
182
|
+
const { done, value } = await reader.read();
|
|
183
|
+
if (done) break;
|
|
184
|
+
if (value) {
|
|
185
|
+
chunks.push(value);
|
|
186
|
+
received += value.length;
|
|
187
|
+
if (total && onProgress) {
|
|
188
|
+
onProgress(`Downloading registry... ${Math.round(received / total * 100)}%`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (onProgress) onProgress("Parsing registry entries...");
|
|
193
|
+
const buf = new Uint8Array(received);
|
|
194
|
+
let pos = 0;
|
|
195
|
+
for (const c of chunks) {
|
|
196
|
+
buf.set(c, pos);
|
|
197
|
+
pos += c.length;
|
|
198
|
+
}
|
|
199
|
+
const text = new TextDecoder().decode(buf);
|
|
200
|
+
const data = JSON.parse(text);
|
|
178
201
|
if (Array.isArray(data)) return data;
|
|
179
202
|
}
|
|
180
203
|
} catch {
|
|
181
204
|
}
|
|
182
205
|
return new Promise((resolve2, reject) => {
|
|
206
|
+
if (onProgress) onProgress("Downloading registry (curl)...");
|
|
183
207
|
(0, import_child_process.execFile)("curl", ["-sL", "--compressed", url], { encoding: "utf-8", maxBuffer: 20 * 1024 * 1024 }, (err, stdout) => {
|
|
184
208
|
if (err) reject(new Error(`curl failed: ${err.message}`));
|
|
185
209
|
else {
|
|
210
|
+
if (onProgress) onProgress("Parsing registry entries...");
|
|
186
211
|
try {
|
|
187
212
|
const data = JSON.parse(stdout);
|
|
188
213
|
if (!Array.isArray(data)) reject(new Error("Invalid registry format"));
|
|
@@ -194,10 +219,11 @@ async function fetchRegistryJSON(url) {
|
|
|
194
219
|
});
|
|
195
220
|
});
|
|
196
221
|
}
|
|
197
|
-
async function updateRegistry() {
|
|
222
|
+
async function updateRegistry(onProgress) {
|
|
198
223
|
const localPath = process.env.COC_REGISTRY_PATH || getLocalRegistryPath();
|
|
199
224
|
if (localPath) {
|
|
200
225
|
if (!fs.existsSync(localPath)) throw new Error(`Local registry not found: ${localPath}`);
|
|
226
|
+
if (onProgress) onProgress("Reading local registry...");
|
|
201
227
|
const data2 = JSON.parse(fs.readFileSync(localPath, "utf-8"));
|
|
202
228
|
if (!Array.isArray(data2)) throw new Error("Invalid registry format");
|
|
203
229
|
fs.mkdirSync(path.dirname(CACHE_PATH), { recursive: true });
|
|
@@ -205,11 +231,12 @@ async function updateRegistry() {
|
|
|
205
231
|
cached = data2;
|
|
206
232
|
return data2.length;
|
|
207
233
|
}
|
|
208
|
-
const data = await fetchRegistryJSON(REMOTE_REGISTRY_URL);
|
|
234
|
+
const data = await fetchRegistryJSON(REMOTE_REGISTRY_URL, onProgress);
|
|
209
235
|
if (!Array.isArray(data)) throw new Error("Invalid registry format");
|
|
210
236
|
fs.mkdirSync(path.dirname(CACHE_PATH), { recursive: true });
|
|
211
237
|
fs.writeFileSync(CACHE_PATH, JSON.stringify(data, null, 2));
|
|
212
238
|
cached = data;
|
|
239
|
+
if (onProgress) onProgress(`Registry updated: ${data.length} packages`);
|
|
213
240
|
return data.length;
|
|
214
241
|
}
|
|
215
242
|
function satisfiesVersion(required) {
|
|
@@ -236,13 +263,19 @@ function getPackage(name) {
|
|
|
236
263
|
var path2 = __toESM(require("path"));
|
|
237
264
|
var fs2 = __toESM(require("fs"));
|
|
238
265
|
var os2 = __toESM(require("os"));
|
|
239
|
-
|
|
240
|
-
|
|
266
|
+
var EXT_DIR = path2.join(os2.homedir(), ".config", "coc", "extensions", "node_modules");
|
|
267
|
+
function getInstalledSet() {
|
|
268
|
+
try {
|
|
269
|
+
const entries = fs2.readdirSync(EXT_DIR);
|
|
270
|
+
return new Set(entries.filter((n) => n.startsWith("coc-")).map((n) => n.slice(4)));
|
|
271
|
+
} catch {
|
|
272
|
+
return /* @__PURE__ */ new Set();
|
|
273
|
+
}
|
|
241
274
|
}
|
|
242
|
-
var PAGE_SIZE = 50;
|
|
243
275
|
function createInitialState() {
|
|
276
|
+
const installedSet = getInstalledSet();
|
|
244
277
|
const packages = getAllPackages().map((info) => {
|
|
245
|
-
const installed =
|
|
278
|
+
const installed = installedSet.has(info.name);
|
|
246
279
|
let commit;
|
|
247
280
|
let commitMsg;
|
|
248
281
|
let commitDate;
|
|
@@ -269,17 +302,22 @@ function createInitialState() {
|
|
|
269
302
|
marked: false
|
|
270
303
|
};
|
|
271
304
|
});
|
|
272
|
-
return { packages, searchQuery: "", showHelp: false, activePill: null, dirty: false, viewFilter: "all", sortBy: "default",
|
|
305
|
+
return { packages, searchQuery: "", showHelp: false, activePill: null, dirty: false, viewFilter: "all", sortBy: "default", scrollOffset: 0 };
|
|
273
306
|
}
|
|
274
307
|
var StateManager = class {
|
|
275
308
|
constructor(initial) {
|
|
276
309
|
this.listeners = /* @__PURE__ */ new Set();
|
|
277
310
|
this.scheduled = false;
|
|
311
|
+
this.cachedFiltered = null;
|
|
312
|
+
this.cachedFilterKey = "";
|
|
278
313
|
this.state = initial;
|
|
279
314
|
}
|
|
280
315
|
getState() {
|
|
281
316
|
return this.state;
|
|
282
317
|
}
|
|
318
|
+
filterKey() {
|
|
319
|
+
return `${this.state.viewFilter}|${this.state.searchQuery}|${this.state.sortBy}`;
|
|
320
|
+
}
|
|
283
321
|
subscribe(fn) {
|
|
284
322
|
this.listeners.add(fn);
|
|
285
323
|
return () => this.listeners.delete(fn);
|
|
@@ -299,11 +337,15 @@ var StateManager = class {
|
|
|
299
337
|
}
|
|
300
338
|
});
|
|
301
339
|
}
|
|
340
|
+
invalidateFilterCache() {
|
|
341
|
+
this.cachedFilterKey = "";
|
|
342
|
+
}
|
|
302
343
|
setPackageStatus(name, status, extra) {
|
|
344
|
+
this.invalidateFilterCache();
|
|
303
345
|
this.mutate((s) => {
|
|
304
346
|
const pkg = s.packages.find((p) => p.info.name === name);
|
|
305
347
|
if (pkg) {
|
|
306
|
-
if (status === "installing" || status === "updating" || status === "uninstalling") {
|
|
348
|
+
if ((status === "installing" || status === "updating" || status === "uninstalling") && pkg.status !== status) {
|
|
307
349
|
pkg.progressLog = [];
|
|
308
350
|
}
|
|
309
351
|
pkg.status = status;
|
|
@@ -340,25 +382,25 @@ var StateManager = class {
|
|
|
340
382
|
setViewFilter(filter) {
|
|
341
383
|
this.mutate((s) => {
|
|
342
384
|
s.viewFilter = filter;
|
|
343
|
-
s.
|
|
385
|
+
s.scrollOffset = 0;
|
|
344
386
|
});
|
|
345
387
|
}
|
|
346
388
|
cycleViewFilter() {
|
|
347
389
|
this.mutate((s) => {
|
|
348
390
|
s.viewFilter = s.viewFilter === "all" ? "installed" : s.viewFilter === "installed" ? "not-installed" : "all";
|
|
349
|
-
s.
|
|
391
|
+
s.scrollOffset = 0;
|
|
350
392
|
});
|
|
351
393
|
}
|
|
352
394
|
setSortBy(sortBy) {
|
|
353
395
|
this.mutate((s) => {
|
|
354
396
|
s.sortBy = sortBy;
|
|
355
|
-
s.
|
|
397
|
+
s.scrollOffset = 0;
|
|
356
398
|
});
|
|
357
399
|
}
|
|
358
400
|
cycleSortBy() {
|
|
359
401
|
this.mutate((s) => {
|
|
360
402
|
s.sortBy = s.sortBy === "default" ? "name" : s.sortBy === "name" ? "status" : s.sortBy === "status" ? "type" : "default";
|
|
361
|
-
s.
|
|
403
|
+
s.scrollOffset = 0;
|
|
362
404
|
});
|
|
363
405
|
}
|
|
364
406
|
setStatusMessage(msg) {
|
|
@@ -379,15 +421,19 @@ var StateManager = class {
|
|
|
379
421
|
setSearchQuery(query) {
|
|
380
422
|
this.mutate((s) => {
|
|
381
423
|
s.searchQuery = query;
|
|
382
|
-
s.
|
|
424
|
+
s.scrollOffset = 0;
|
|
383
425
|
});
|
|
384
426
|
}
|
|
385
|
-
|
|
427
|
+
setScrollOffset(n) {
|
|
386
428
|
this.mutate((s) => {
|
|
387
|
-
s.
|
|
429
|
+
s.scrollOffset = n;
|
|
388
430
|
});
|
|
389
431
|
}
|
|
390
432
|
getFilteredPackages() {
|
|
433
|
+
const key = this.filterKey();
|
|
434
|
+
if (this.cachedFiltered && this.cachedFilterKey === key) {
|
|
435
|
+
return this.cachedFiltered;
|
|
436
|
+
}
|
|
391
437
|
let pkgs = this.state.packages;
|
|
392
438
|
if (this.state.viewFilter === "not-installed") {
|
|
393
439
|
pkgs = pkgs.filter((p) => p.status === "not-installed");
|
|
@@ -409,6 +455,8 @@ var StateManager = class {
|
|
|
409
455
|
} else if (sortBy === "type") {
|
|
410
456
|
pkgs = [...pkgs].sort((a, b) => a.info.type.localeCompare(b.info.type));
|
|
411
457
|
}
|
|
458
|
+
this.cachedFiltered = pkgs;
|
|
459
|
+
this.cachedFilterKey = key;
|
|
412
460
|
return pkgs;
|
|
413
461
|
}
|
|
414
462
|
getPackage(name) {
|
|
@@ -429,6 +477,8 @@ var StateManager = class {
|
|
|
429
477
|
return this.state.packages.filter((p) => p.marked).map((p) => p.info.name);
|
|
430
478
|
}
|
|
431
479
|
refreshPackages() {
|
|
480
|
+
this.invalidateFilterCache();
|
|
481
|
+
const installedSet = getInstalledSet();
|
|
432
482
|
this.mutate((s) => {
|
|
433
483
|
const updated = getAllPackages();
|
|
434
484
|
const oldMap = new Map(s.packages.map((p) => [p.info.name, p]));
|
|
@@ -440,7 +490,7 @@ var StateManager = class {
|
|
|
440
490
|
}
|
|
441
491
|
return {
|
|
442
492
|
info,
|
|
443
|
-
status:
|
|
493
|
+
status: installedSet.has(info.name) ? "installed" : "not-installed",
|
|
444
494
|
progressLog: [],
|
|
445
495
|
expanded: false,
|
|
446
496
|
logExpanded: false,
|
|
@@ -522,16 +572,23 @@ async function downloadSource(info, name, onProgress) {
|
|
|
522
572
|
const srcDir = sourceDir(name);
|
|
523
573
|
const cache = cacheDir(name);
|
|
524
574
|
const repoUrl = `https://github.com/${info.source.repo}.git`;
|
|
525
|
-
|
|
575
|
+
let output = "";
|
|
526
576
|
if (fs3.existsSync(srcDir)) {
|
|
527
577
|
onProgress(1, 5, "Updating source...", `git -C ${srcDir} pull`);
|
|
578
|
+
const log = (chunk) => {
|
|
579
|
+
output += chunk;
|
|
580
|
+
onProgress(1, 5, "Updating source...", chunk.trim());
|
|
581
|
+
};
|
|
528
582
|
await run("git", ["-C", srcDir, "pull"], cache, log);
|
|
529
583
|
} else {
|
|
530
584
|
onProgress(1, 5, "Cloning repository...", `git clone --depth=1 ${repoUrl}`);
|
|
531
585
|
fs3.mkdirSync(cache, { recursive: true });
|
|
586
|
+
const log = (chunk) => onProgress(1, 5, "Cloning repository...", chunk.trim());
|
|
532
587
|
await run("git", ["clone", "--depth=1", repoUrl, srcDir], cache, log);
|
|
533
588
|
}
|
|
534
|
-
|
|
589
|
+
const dir = info.source.subdir ? path3.join(srcDir, info.source.subdir) : srcDir;
|
|
590
|
+
const updated = !output.includes("Already up to date.");
|
|
591
|
+
return { dir, updated };
|
|
535
592
|
}
|
|
536
593
|
async function convertSource(inputDir, name, info, onProgress) {
|
|
537
594
|
const build = buildDir(name);
|
|
@@ -593,8 +650,11 @@ async function buildPackage(name, inputDir, info, onProgress) {
|
|
|
593
650
|
onProgress(3, 5, "Installing dependencies...", "npm install --legacy-peer-deps");
|
|
594
651
|
await run("npm", ["install", "--legacy-peer-deps"], build, npmLog);
|
|
595
652
|
onProgress(3, 5, "Running postinstall...", "npm run postinstall");
|
|
596
|
-
|
|
597
|
-
|
|
653
|
+
try {
|
|
654
|
+
await run("npm", ["run", "postinstall", "--if-present"], build, npmLog);
|
|
655
|
+
} catch (e) {
|
|
656
|
+
onProgress(3, 5, `Warning: postinstall failed (${e.message})`, "may affect plugin functionality");
|
|
657
|
+
}
|
|
598
658
|
if (info.pipPackages?.length) {
|
|
599
659
|
const pipLog = (chunk) => onProgress(3, 5, chunk.trim(), "");
|
|
600
660
|
const pythonPaths = [
|
|
@@ -744,9 +804,9 @@ async function installToCoc(name, onProgress) {
|
|
|
744
804
|
const depName = `coc-${name}`;
|
|
745
805
|
if (!pkg.dependencies[depName]) {
|
|
746
806
|
pkg.dependencies[depName] = `file:${dest}`;
|
|
747
|
-
pkg.lastUpdate = Date.now();
|
|
748
|
-
fs3.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
749
807
|
}
|
|
808
|
+
pkg.lastUpdate = Date.now();
|
|
809
|
+
fs3.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
750
810
|
}
|
|
751
811
|
function metaPath(name) {
|
|
752
812
|
return path3.join(cacheDir(name), "meta.json");
|
|
@@ -770,13 +830,12 @@ async function installPackage(state, name) {
|
|
|
770
830
|
state.setPackageStatus(name, "installing", {
|
|
771
831
|
progress: `[${step}/${total}] ${msg}`,
|
|
772
832
|
logEntry: `[${step}/${total}] ${msg}
|
|
773
|
-
$ ${cmd}
|
|
774
|
-
appendLog: true
|
|
833
|
+
$ ${cmd}`
|
|
775
834
|
});
|
|
776
835
|
};
|
|
777
836
|
state.setPackageStatus(name, "installing", { progress: "Starting..." });
|
|
778
837
|
try {
|
|
779
|
-
const input = await downloadSource(info, name, prog);
|
|
838
|
+
const { dir: input } = await downloadSource(info, name, prog);
|
|
780
839
|
await convertSource(input, name, info, prog);
|
|
781
840
|
await buildPackage(name, input, info, prog);
|
|
782
841
|
await installToCoc(name, prog);
|
|
@@ -849,13 +908,17 @@ async function updatePackage(state, name) {
|
|
|
849
908
|
state.setPackageStatus(name, "updating", {
|
|
850
909
|
progress: `[${step}/${total}] ${msg}`,
|
|
851
910
|
logEntry: `[${step}/${total}] ${msg}
|
|
852
|
-
$ ${cmd}
|
|
853
|
-
appendLog: true
|
|
911
|
+
$ ${cmd}`
|
|
854
912
|
});
|
|
855
913
|
};
|
|
856
914
|
state.setPackageStatus(name, "updating", { progress: "Starting..." });
|
|
857
915
|
try {
|
|
858
|
-
const input = await downloadSource(info, name, prog);
|
|
916
|
+
const { dir: input, updated } = await downloadSource(info, name, prog);
|
|
917
|
+
if (!updated) {
|
|
918
|
+
state.setPackageStatus(name, "installed");
|
|
919
|
+
import_coc.window.showInformationMessage(`coc-${name} is already up to date`);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
859
922
|
await convertSource(input, name, info, prog);
|
|
860
923
|
await buildPackage(name, input, info, prog);
|
|
861
924
|
await installToCoc(name, prog);
|
|
@@ -909,41 +972,51 @@ async function runWithOutput(cmd, args, cwd) {
|
|
|
909
972
|
async function runConcurrent(items, fn, concurrency = 3) {
|
|
910
973
|
const pool = /* @__PURE__ */ new Set();
|
|
911
974
|
for (const item of items) {
|
|
912
|
-
const p = fn(item).
|
|
975
|
+
const p = fn(item).catch(() => {
|
|
976
|
+
}).finally(() => pool.delete(p));
|
|
913
977
|
pool.add(p);
|
|
914
978
|
if (pool.size >= concurrency) {
|
|
915
979
|
await Promise.race(pool);
|
|
916
980
|
}
|
|
917
981
|
}
|
|
918
|
-
await Promise.
|
|
982
|
+
await Promise.allSettled(pool);
|
|
919
983
|
}
|
|
984
|
+
var checkUpdatesBusy = false;
|
|
920
985
|
async function checkUpdates(state) {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
state.setStatusMessage(
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
const
|
|
930
|
-
if (
|
|
931
|
-
|
|
986
|
+
if (checkUpdatesBusy) return;
|
|
987
|
+
checkUpdatesBusy = true;
|
|
988
|
+
try {
|
|
989
|
+
const s = state.getState();
|
|
990
|
+
const results = {};
|
|
991
|
+
state.setStatusMessage("Checking for updates...");
|
|
992
|
+
for (const pkg of s.packages) {
|
|
993
|
+
if (pkg.status !== "installed" || !pkg.commit) continue;
|
|
994
|
+
const live = state.getPackage(pkg.info.name);
|
|
995
|
+
if (!live || live.status !== "installed" || !live.commit) continue;
|
|
996
|
+
state.setStatusMessage(`Checking ${pkg.info.displayName}...`);
|
|
997
|
+
try {
|
|
998
|
+
const out = await runWithOutput("git", ["ls-remote", `https://github.com/${pkg.info.source.repo}.git`, "HEAD"], os3.homedir());
|
|
999
|
+
const remote = out.split(" ")[0];
|
|
1000
|
+
if (remote) results[pkg.info.name] = remote.substring(0, 7) !== live.commit;
|
|
1001
|
+
} catch {
|
|
1002
|
+
}
|
|
932
1003
|
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1004
|
+
const updateCount = Object.values(results).filter(Boolean).length;
|
|
1005
|
+
state.mutate((s2) => {
|
|
1006
|
+
for (const p of s2.packages) {
|
|
1007
|
+
if (results[p.info.name] !== void 0) p.hasUpdate = results[p.info.name];
|
|
1008
|
+
}
|
|
1009
|
+
s2.statusMessage = void 0;
|
|
1010
|
+
});
|
|
1011
|
+
if (updateCount > 0) {
|
|
1012
|
+
state.setStatusMessage(`Found ${updateCount} package(s) with updates. Use 'u' to update.`);
|
|
1013
|
+
setTimeout(() => state.setStatusMessage(), 5e3);
|
|
1014
|
+
} else {
|
|
1015
|
+
state.setStatusMessage("All packages up to date.");
|
|
1016
|
+
setTimeout(() => state.setStatusMessage(), 3e3);
|
|
938
1017
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
if (updateCount > 0) {
|
|
942
|
-
state.setStatusMessage(`Found ${updateCount} package(s) with updates. Use 'u' to update.`);
|
|
943
|
-
setTimeout(() => state.setStatusMessage(), 5e3);
|
|
944
|
-
} else {
|
|
945
|
-
state.setStatusMessage("All packages up to date.");
|
|
946
|
-
setTimeout(() => state.setStatusMessage(), 3e3);
|
|
1018
|
+
} finally {
|
|
1019
|
+
checkUpdatesBusy = false;
|
|
947
1020
|
}
|
|
948
1021
|
}
|
|
949
1022
|
|
|
@@ -973,6 +1046,13 @@ var LineBuffer = class {
|
|
|
973
1046
|
currentLine() {
|
|
974
1047
|
return this.li;
|
|
975
1048
|
}
|
|
1049
|
+
currentByteLen() {
|
|
1050
|
+
let len = 0;
|
|
1051
|
+
for (const seg of this.lines[this.li]) {
|
|
1052
|
+
len += byteLen(seg.text);
|
|
1053
|
+
}
|
|
1054
|
+
return len;
|
|
1055
|
+
}
|
|
976
1056
|
highlight(pattern, hlGroup) {
|
|
977
1057
|
const segs = this.lines[this.li];
|
|
978
1058
|
let full = "";
|
|
@@ -1039,11 +1119,11 @@ var HELP_TEXT = [
|
|
|
1039
1119
|
" x Toggle mark",
|
|
1040
1120
|
" f Cycle filter: all \u2192 installed \u2192 not-installed",
|
|
1041
1121
|
" s Cycle sort: default \u2192 name \u2192 status \u2192 type",
|
|
1042
|
-
"
|
|
1122
|
+
" j/k Scroll through packages",
|
|
1123
|
+
" / Search filter (then j/k to scroll)",
|
|
1043
1124
|
" gg Jump to first page",
|
|
1044
1125
|
" G Jump to last page",
|
|
1045
|
-
" <Enter>
|
|
1046
|
-
" / Search filter",
|
|
1126
|
+
" <Enter> Open detail popup (description, source, log)",
|
|
1047
1127
|
" q Close window",
|
|
1048
1128
|
" <Esc> Help\u2192Search\u2192Marks\u2192Busy guard\u2192Close",
|
|
1049
1129
|
"",
|
|
@@ -1054,7 +1134,7 @@ var HELP_TEXT = [
|
|
|
1054
1134
|
" pure-lsp Standard LSP protocol (e.g. Prisma, ESLint)",
|
|
1055
1135
|
" direct-api Direct coc.nvim API calls (e.g. HTML CSS Support)"
|
|
1056
1136
|
];
|
|
1057
|
-
var TUI = class {
|
|
1137
|
+
var TUI = class _TUI {
|
|
1058
1138
|
constructor(state) {
|
|
1059
1139
|
this.bufnr = 0;
|
|
1060
1140
|
this.winid = 0;
|
|
@@ -1063,11 +1143,20 @@ var TUI = class {
|
|
|
1063
1143
|
this.unsubscribe = null;
|
|
1064
1144
|
this.pkgLineMap = /* @__PURE__ */ new Map();
|
|
1065
1145
|
this.logLineSet = /* @__PURE__ */ new Set();
|
|
1146
|
+
this.detailWinid = 0;
|
|
1147
|
+
this.detailBufnr = 0;
|
|
1148
|
+
this.detailPkgName = "";
|
|
1149
|
+
this.detailMode = "info";
|
|
1150
|
+
this.windowHeight = 0;
|
|
1151
|
+
this.windowWidth = 0;
|
|
1066
1152
|
this.keyMap = {
|
|
1067
1153
|
q: "q",
|
|
1068
1154
|
esc: "<Esc>",
|
|
1069
1155
|
question: "?",
|
|
1070
1156
|
slash: "/",
|
|
1157
|
+
j: "j",
|
|
1158
|
+
k: "k",
|
|
1159
|
+
"close-detail": "close-detail",
|
|
1071
1160
|
U: "U",
|
|
1072
1161
|
Z: "Z",
|
|
1073
1162
|
i: "i",
|
|
@@ -1080,16 +1169,20 @@ var TUI = class {
|
|
|
1080
1169
|
x: "x",
|
|
1081
1170
|
D: "D",
|
|
1082
1171
|
gg: "gg",
|
|
1083
|
-
G: "G"
|
|
1084
|
-
"[": "pageup",
|
|
1085
|
-
"]": "pagedown"
|
|
1172
|
+
G: "G"
|
|
1086
1173
|
};
|
|
1087
1174
|
this.rendering = false;
|
|
1088
1175
|
this.pendingRender = false;
|
|
1089
|
-
this.
|
|
1090
|
-
this.
|
|
1176
|
+
this.focusIndex = 0;
|
|
1177
|
+
this.focusLineOffset = 0;
|
|
1091
1178
|
this.state = state;
|
|
1092
1179
|
}
|
|
1180
|
+
static {
|
|
1181
|
+
this.HEADER_LINES = 6;
|
|
1182
|
+
}
|
|
1183
|
+
static {
|
|
1184
|
+
this.FOOTER_LINES = 3;
|
|
1185
|
+
}
|
|
1093
1186
|
async open() {
|
|
1094
1187
|
const nvim = import_coc2.workspace.nvim;
|
|
1095
1188
|
this.ns = await nvim.createNamespace("coc-loader");
|
|
@@ -1107,7 +1200,9 @@ var TUI = class {
|
|
|
1107
1200
|
const editorLines = await nvim.call("nvim_get_option", ["lines"]);
|
|
1108
1201
|
const editorCols = await nvim.call("nvim_get_option", ["columns"]);
|
|
1109
1202
|
const height = Math.min(Math.floor(editorLines * 0.85), 40);
|
|
1203
|
+
this.windowHeight = height - 2;
|
|
1110
1204
|
const width = Math.min(Math.floor(editorCols * 0.85), 120);
|
|
1205
|
+
this.windowWidth = width - 2;
|
|
1111
1206
|
const row = Math.max(Math.floor((editorLines - height) / 2), 0);
|
|
1112
1207
|
const col = Math.max(Math.floor((editorCols - width) / 2), 0);
|
|
1113
1208
|
const win = await nvim.openFloatWindow(buf, true, {
|
|
@@ -1116,7 +1211,7 @@ var TUI = class {
|
|
|
1116
1211
|
height,
|
|
1117
1212
|
row,
|
|
1118
1213
|
col,
|
|
1119
|
-
border: "
|
|
1214
|
+
border: "rounded",
|
|
1120
1215
|
style: "minimal"
|
|
1121
1216
|
});
|
|
1122
1217
|
this.winid = win.id;
|
|
@@ -1147,7 +1242,7 @@ var TUI = class {
|
|
|
1147
1242
|
const curBuf = await nvim.call("winbufnr", [curWin]);
|
|
1148
1243
|
const bt = await nvim.call("getbufvar", [curBuf, "&buftype"]);
|
|
1149
1244
|
if (bt !== "nofile" && bt !== "prompt") {
|
|
1150
|
-
this.close();
|
|
1245
|
+
await this.close();
|
|
1151
1246
|
}
|
|
1152
1247
|
}
|
|
1153
1248
|
})
|
|
@@ -1162,7 +1257,12 @@ var TUI = class {
|
|
|
1162
1257
|
}
|
|
1163
1258
|
await this.setupKeymaps();
|
|
1164
1259
|
await this.render();
|
|
1165
|
-
|
|
1260
|
+
this.state.setStatusMessage("Fetching registry...");
|
|
1261
|
+
const onProgress = (msg) => {
|
|
1262
|
+
this.state.setStatusMessage(msg);
|
|
1263
|
+
};
|
|
1264
|
+
updateRegistry(onProgress).then(() => {
|
|
1265
|
+
this.state.setStatusMessage();
|
|
1166
1266
|
this.state.refreshPackages();
|
|
1167
1267
|
this.render();
|
|
1168
1268
|
}).catch(() => {
|
|
@@ -1176,14 +1276,23 @@ var TUI = class {
|
|
|
1176
1276
|
return cursor[0] - 1;
|
|
1177
1277
|
}
|
|
1178
1278
|
async handleKey(id) {
|
|
1279
|
+
if (!this.winid) return;
|
|
1179
1280
|
const line0 = await this.getCursorLine0();
|
|
1180
1281
|
const s = this.state.getState();
|
|
1181
1282
|
if (id === "q") {
|
|
1182
|
-
this.close();
|
|
1283
|
+
await this.close();
|
|
1183
1284
|
return;
|
|
1184
1285
|
}
|
|
1185
1286
|
if (id === "I") {
|
|
1186
1287
|
this.state.setActivePill("I");
|
|
1288
|
+
const marked = this.state.getMarkedNames();
|
|
1289
|
+
if (marked.length === 0) {
|
|
1290
|
+
import_coc2.window.showInformationMessage("No packages marked. Use x to mark packages.");
|
|
1291
|
+
this.state.setActivePill(null);
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
await runConcurrent(marked, (name) => installPackage(this.state, name));
|
|
1295
|
+
this.state.setActivePill(null);
|
|
1187
1296
|
return;
|
|
1188
1297
|
}
|
|
1189
1298
|
if (id === "H") {
|
|
@@ -1211,7 +1320,7 @@ var TUI = class {
|
|
|
1211
1320
|
import_coc2.window.showInformationMessage("Operation in progress, wait for it to finish");
|
|
1212
1321
|
return;
|
|
1213
1322
|
}
|
|
1214
|
-
this.close();
|
|
1323
|
+
await this.close();
|
|
1215
1324
|
return;
|
|
1216
1325
|
}
|
|
1217
1326
|
if (id === "question") {
|
|
@@ -1228,36 +1337,62 @@ var TUI = class {
|
|
|
1228
1337
|
}
|
|
1229
1338
|
if (id === "f") {
|
|
1230
1339
|
this.state.cycleViewFilter();
|
|
1340
|
+
this.focusIndex = 0;
|
|
1231
1341
|
return;
|
|
1232
1342
|
}
|
|
1233
1343
|
if (id === "s") {
|
|
1234
1344
|
this.state.cycleSortBy();
|
|
1345
|
+
this.focusIndex = 0;
|
|
1235
1346
|
return;
|
|
1236
1347
|
}
|
|
1237
1348
|
if (id === "gg") {
|
|
1238
|
-
this.state.
|
|
1239
|
-
this.
|
|
1349
|
+
this.state.setScrollOffset(0);
|
|
1350
|
+
this.focusIndex = 0;
|
|
1240
1351
|
return;
|
|
1241
1352
|
}
|
|
1242
1353
|
if (id === "G") {
|
|
1243
1354
|
const filtered = this.state.getFilteredPackages();
|
|
1244
|
-
const
|
|
1245
|
-
this.state.
|
|
1355
|
+
const visibleCount = Math.max(1, this.windowHeight - _TUI.HEADER_LINES - _TUI.FOOTER_LINES);
|
|
1356
|
+
this.state.setScrollOffset(Math.max(0, filtered.length - visibleCount));
|
|
1357
|
+
this.focusIndex = Math.max(0, filtered.length - 1);
|
|
1246
1358
|
return;
|
|
1247
1359
|
}
|
|
1248
|
-
if (id === "
|
|
1360
|
+
if (id === "j") {
|
|
1249
1361
|
const filtered = this.state.getFilteredPackages();
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
this.state.
|
|
1362
|
+
if (this.focusIndex < filtered.length - 1) {
|
|
1363
|
+
this.focusIndex++;
|
|
1364
|
+
this.focusLineOffset = 0;
|
|
1365
|
+
const s2 = this.state.getState();
|
|
1366
|
+
const visibleCount = Math.max(1, this.windowHeight - _TUI.HEADER_LINES - _TUI.FOOTER_LINES);
|
|
1367
|
+
if (this.focusIndex >= s2.scrollOffset + visibleCount) {
|
|
1368
|
+
s2.scrollOffset = Math.min(Math.max(0, filtered.length - visibleCount), s2.scrollOffset + 1);
|
|
1369
|
+
await this.render();
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
const focused = filtered[this.focusIndex];
|
|
1373
|
+
const pkgLine = [...this.pkgLineMap.entries()].find(([l, n]) => n === focused.info.name)?.[0];
|
|
1374
|
+
if (pkgLine !== void 0) {
|
|
1375
|
+
await import_coc2.workspace.nvim.call("nvim_win_set_cursor", [this.winid, [pkgLine + 1, 0]]);
|
|
1376
|
+
}
|
|
1254
1377
|
}
|
|
1255
1378
|
return;
|
|
1256
1379
|
}
|
|
1257
|
-
if (id === "
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
this.
|
|
1380
|
+
if (id === "k") {
|
|
1381
|
+
if (this.focusIndex > 0) {
|
|
1382
|
+
this.focusIndex--;
|
|
1383
|
+
this.focusLineOffset = 0;
|
|
1384
|
+
const s2 = this.state.getState();
|
|
1385
|
+
if (this.focusIndex < s2.scrollOffset) {
|
|
1386
|
+
s2.scrollOffset = Math.max(0, s2.scrollOffset - 1);
|
|
1387
|
+
await this.render();
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
const filtered = this.state.getFilteredPackages();
|
|
1391
|
+
const focused = filtered[this.focusIndex];
|
|
1392
|
+
const pkgLine = [...this.pkgLineMap.entries()].find(([l, n]) => n === focused.info.name)?.[0];
|
|
1393
|
+
if (pkgLine !== void 0) {
|
|
1394
|
+
await import_coc2.workspace.nvim.call("nvim_win_set_cursor", [this.winid, [pkgLine + 1, 0]]);
|
|
1395
|
+
}
|
|
1261
1396
|
}
|
|
1262
1397
|
return;
|
|
1263
1398
|
}
|
|
@@ -1323,12 +1458,12 @@ var TUI = class {
|
|
|
1323
1458
|
await installPackage(this.state, pkgName);
|
|
1324
1459
|
return;
|
|
1325
1460
|
}
|
|
1461
|
+
if (id === "close-detail") {
|
|
1462
|
+
this.closeDetailPopup();
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1326
1465
|
if (id === "cr") {
|
|
1327
|
-
if (this.
|
|
1328
|
-
this.state.toggleLog(pkgName);
|
|
1329
|
-
} else {
|
|
1330
|
-
this.state.toggleExpand(pkgName);
|
|
1331
|
-
}
|
|
1466
|
+
if (pkgName) await this.showDetailPopup(pkgName);
|
|
1332
1467
|
return;
|
|
1333
1468
|
}
|
|
1334
1469
|
}
|
|
@@ -1352,10 +1487,10 @@ var TUI = class {
|
|
|
1352
1487
|
["s", "s"],
|
|
1353
1488
|
["x", "x"],
|
|
1354
1489
|
["D", "D"],
|
|
1490
|
+
["j", "j"],
|
|
1491
|
+
["k", "k"],
|
|
1355
1492
|
["gg", "gg"],
|
|
1356
1493
|
["G", "G"],
|
|
1357
|
-
["[", "pageup"],
|
|
1358
|
-
["]", "pagedown"],
|
|
1359
1494
|
["<CR>", "cr"]
|
|
1360
1495
|
];
|
|
1361
1496
|
for (const [vimKey, id] of entries) {
|
|
@@ -1372,6 +1507,13 @@ var TUI = class {
|
|
|
1372
1507
|
d.dispose();
|
|
1373
1508
|
}
|
|
1374
1509
|
this.disposables = [];
|
|
1510
|
+
if (this.detailWinid) {
|
|
1511
|
+
try {
|
|
1512
|
+
import_coc2.workspace.nvim.call("nvim_win_close", [this.detailWinid, true]);
|
|
1513
|
+
} catch {
|
|
1514
|
+
}
|
|
1515
|
+
this.detailWinid = 0;
|
|
1516
|
+
}
|
|
1375
1517
|
if (this.winid) {
|
|
1376
1518
|
try {
|
|
1377
1519
|
await import_coc2.workspace.nvim.call("nvim_win_close", [this.winid, true]);
|
|
@@ -1396,26 +1538,47 @@ var TUI = class {
|
|
|
1396
1538
|
const state = this.state.getState();
|
|
1397
1539
|
const filtered = this.state.getFilteredPackages();
|
|
1398
1540
|
const result = state.showHelp ? this.renderHelp() : this.renderPackageList(state, filtered);
|
|
1541
|
+
if (this.windowWidth > 0) {
|
|
1542
|
+
result.highlights = result.highlights.filter((h) => h.colStart < this.windowWidth);
|
|
1543
|
+
for (const h of result.highlights) {
|
|
1544
|
+
if (h.colEnd > this.windowWidth) h.colEnd = this.windowWidth;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1399
1547
|
nvim.pauseNotification();
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1548
|
+
try {
|
|
1549
|
+
nvim.call("nvim_buf_set_option", [this.bufnr, "modifiable", true], true);
|
|
1550
|
+
nvim.call("nvim_buf_clear_namespace", [this.bufnr, this.ns, 0, -1], true);
|
|
1551
|
+
nvim.call("nvim_buf_set_lines", [this.bufnr, 0, -1, false, result.lines], true);
|
|
1552
|
+
nvim.call("nvim_buf_set_option", [this.bufnr, "modifiable", false], true);
|
|
1553
|
+
for (const h of result.highlights) {
|
|
1554
|
+
nvim.call("nvim_buf_set_extmark", [this.bufnr, this.ns, h.line, h.colStart, {
|
|
1555
|
+
end_col: h.colEnd,
|
|
1556
|
+
hl_group: h.hlGroup,
|
|
1557
|
+
hl_mode: "combine"
|
|
1558
|
+
}], true);
|
|
1559
|
+
}
|
|
1560
|
+
} finally {
|
|
1561
|
+
await nvim.resumeNotification();
|
|
1562
|
+
}
|
|
1563
|
+
if (this.detailWinid) {
|
|
1564
|
+
this.updateDetailPopup().catch(() => {
|
|
1565
|
+
});
|
|
1410
1566
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1567
|
+
if (!state.showHelp && result.pkgLineMap.size > 0) {
|
|
1568
|
+
const visibleCount = Math.max(1, this.windowHeight - _TUI.HEADER_LINES - _TUI.FOOTER_LINES);
|
|
1569
|
+
if (this.focusIndex < state.scrollOffset) {
|
|
1570
|
+
state.scrollOffset = this.focusIndex;
|
|
1571
|
+
} else if (this.focusIndex >= state.scrollOffset + visibleCount) {
|
|
1572
|
+
state.scrollOffset = Math.max(0, this.focusIndex - visibleCount + 1);
|
|
1573
|
+
}
|
|
1574
|
+
const visible = this.state.getFilteredPackages();
|
|
1575
|
+
const idx = Math.min(this.focusIndex, visible.length - 1);
|
|
1576
|
+
const focused = visible[idx];
|
|
1577
|
+
if (focused) {
|
|
1578
|
+
const targetLine = [...result.pkgLineMap.entries()].find(([l, n]) => n === focused.info.name)?.[0];
|
|
1579
|
+
if (targetLine !== void 0) {
|
|
1580
|
+
await nvim.call("nvim_win_set_cursor", [this.winid, [targetLine + 1, 0]]);
|
|
1581
|
+
}
|
|
1419
1582
|
}
|
|
1420
1583
|
}
|
|
1421
1584
|
this.pkgLineMap = result.pkgLineMap;
|
|
@@ -1483,19 +1646,16 @@ var TUI = class {
|
|
|
1483
1646
|
}
|
|
1484
1647
|
buf.nl();
|
|
1485
1648
|
buf.nl();
|
|
1486
|
-
const
|
|
1487
|
-
const
|
|
1488
|
-
const start =
|
|
1489
|
-
const
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
} else {
|
|
1494
|
-
buf.append(`${filtered.length} packages`, "CocConverterTotal");
|
|
1495
|
-
}
|
|
1649
|
+
const visibleCount = Math.max(1, this.windowHeight - _TUI.HEADER_LINES - _TUI.FOOTER_LINES);
|
|
1650
|
+
const maxOffset = Math.max(0, filtered.length - visibleCount);
|
|
1651
|
+
const start = Math.min(state.scrollOffset, maxOffset);
|
|
1652
|
+
const end = Math.min(start + visibleCount, filtered.length);
|
|
1653
|
+
const visible = filtered.slice(start, end);
|
|
1654
|
+
const indicator = filtered.length > visibleCount ? `${start + 1}\u2013${end} of ${filtered.length}` : `${filtered.length} packages`;
|
|
1655
|
+
buf.append(indicator, "CocConverterTotal");
|
|
1496
1656
|
buf.nl();
|
|
1497
1657
|
buf.nl();
|
|
1498
|
-
for (const e of
|
|
1658
|
+
for (const e of visible) {
|
|
1499
1659
|
this.renderEntry(buf, pkgLineMap, logSet, e);
|
|
1500
1660
|
}
|
|
1501
1661
|
if (filtered.length === 0 && state.searchQuery) {
|
|
@@ -1504,12 +1664,13 @@ var TUI = class {
|
|
|
1504
1664
|
buf.nl();
|
|
1505
1665
|
buf.append(" " + "\u2500".repeat(50), "Comment");
|
|
1506
1666
|
buf.nl();
|
|
1507
|
-
const
|
|
1508
|
-
|
|
1667
|
+
const filterLabel2 = state.viewFilter === "all" ? "All" : state.viewFilter === "installed" ? "Installed" : "Available";
|
|
1668
|
+
const sortLabel2 = state.sortBy === "default" ? "Default" : state.sortBy === "name" ? "Name" : state.sortBy === "status" ? "Status" : "Type";
|
|
1669
|
+
buf.append(` ${filtered.length} packages \xB7 ${filterLabel2} \xB7 ${sortLabel2}`, "Comment");
|
|
1509
1670
|
const result = buf.render(2);
|
|
1510
1671
|
return { lines: result.lines, pkgLineMap, logLines: logSet, highlights: result.highlights };
|
|
1511
1672
|
}
|
|
1512
|
-
renderEntry(buf, pkgLineMap,
|
|
1673
|
+
renderEntry(buf, pkgLineMap, _logSet, entry) {
|
|
1513
1674
|
const icon = entry.status === "installed" ? "\u25CF" : entry.status === "failed" ? "\u2717" : "\u25CB";
|
|
1514
1675
|
const iconHl = entry.status === "installed" ? "CocConverterInstalled" : entry.status === "failed" ? "ErrorMsg" : "CocConverterAvailable";
|
|
1515
1676
|
const pkgLine = buf.currentLine();
|
|
@@ -1523,67 +1684,153 @@ var TUI = class {
|
|
|
1523
1684
|
buf.append(icon, iconHl);
|
|
1524
1685
|
buf.append(" ");
|
|
1525
1686
|
buf.append(entry.info.displayName);
|
|
1687
|
+
let statusText = "";
|
|
1688
|
+
let statusHl = "";
|
|
1689
|
+
if (entry.progress) {
|
|
1690
|
+
statusText = ` ${entry.progress}`;
|
|
1691
|
+
statusHl = "Comment";
|
|
1692
|
+
} else if (entry.status === "failed" && entry.error) {
|
|
1693
|
+
statusText = ` \u2717 ${entry.error}`;
|
|
1694
|
+
statusHl = "ErrorMsg";
|
|
1695
|
+
}
|
|
1526
1696
|
buf.append(" ");
|
|
1527
1697
|
buf.append(entry.info.type, "CocConverterType");
|
|
1698
|
+
if (statusText) {
|
|
1699
|
+
buf.append(statusText, statusHl);
|
|
1700
|
+
}
|
|
1528
1701
|
if (entry.hasUpdate) {
|
|
1529
1702
|
buf.append(" \u2191", "CocConverterKey");
|
|
1530
1703
|
}
|
|
1531
|
-
if (entry.
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1704
|
+
if (entry.commit && entry.commitMsg && entry.status === "installed") {
|
|
1705
|
+
const cr = entry.commitDate ? ` (${entry.commitDate})` : "";
|
|
1706
|
+
let msg = entry.commitMsg;
|
|
1707
|
+
if (this.windowWidth > 0) {
|
|
1708
|
+
const prefixLen = buf.currentByteLen();
|
|
1709
|
+
const commitPrefix = ` ${entry.commit} `;
|
|
1710
|
+
const suffix = cr;
|
|
1711
|
+
const available = this.windowWidth - 2 - prefixLen - Buffer.from(commitPrefix).length - Buffer.from(suffix).length - 3;
|
|
1712
|
+
if (available > 0 && Buffer.from(msg).length > available) {
|
|
1713
|
+
while (Buffer.from(msg).length > available && msg.length > 0) {
|
|
1714
|
+
msg = msg.slice(0, -1);
|
|
1715
|
+
}
|
|
1716
|
+
msg += "\u2026";
|
|
1717
|
+
}
|
|
1537
1718
|
}
|
|
1538
|
-
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
`homepage ${entry.info.url}`,
|
|
1550
|
-
entry.info.serverBinary ? `server ${entry.info.serverBinary.repo} (binary release)` : null
|
|
1551
|
-
];
|
|
1552
|
-
for (const text of extras.filter(Boolean)) {
|
|
1553
|
-
const ln = buf.currentLine();
|
|
1554
|
-
buf.nl(` ${text}`);
|
|
1555
|
-
pkgLineMap.set(ln, entry.info.name);
|
|
1719
|
+
buf.append(` ${entry.commit} ${msg}${cr}`, "Comment");
|
|
1720
|
+
}
|
|
1721
|
+
buf.nl();
|
|
1722
|
+
}
|
|
1723
|
+
buildDetailLines(entry, mode = "info") {
|
|
1724
|
+
const lines = [];
|
|
1725
|
+
if (mode === "log") {
|
|
1726
|
+
for (const log of entry.progressLog) {
|
|
1727
|
+
for (const l of log.split("\n")) {
|
|
1728
|
+
lines.push(` ${l}`);
|
|
1729
|
+
}
|
|
1556
1730
|
}
|
|
1731
|
+
if (entry.error) lines.push("", ` \u2717 ${entry.error}`);
|
|
1732
|
+
return lines;
|
|
1557
1733
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1734
|
+
lines.push(
|
|
1735
|
+
` desc ${entry.info.description}`,
|
|
1736
|
+
` type ${entry.info.type}`,
|
|
1737
|
+
` status ${entry.status}`
|
|
1738
|
+
);
|
|
1739
|
+
if (entry.commit) lines.push(` commit ${entry.commit}`);
|
|
1740
|
+
lines.push(
|
|
1741
|
+
` source ${sourceStr(entry.info.source)}`,
|
|
1742
|
+
` langs ${entry.info.languages.join(", ")}`,
|
|
1743
|
+
` cats ${entry.info.categories.join(", ")}`,
|
|
1744
|
+
` link ${entry.info.url}`
|
|
1745
|
+
);
|
|
1746
|
+
if (entry.info.serverBinary) {
|
|
1747
|
+
lines.push(` server ${entry.info.serverBinary.repo}`);
|
|
1748
|
+
}
|
|
1749
|
+
return lines;
|
|
1750
|
+
}
|
|
1751
|
+
async showDetailPopup(name) {
|
|
1752
|
+
if (this.detailWinid) this.closeDetailPopup();
|
|
1753
|
+
this.detailPkgName = name;
|
|
1754
|
+
const nvim = import_coc2.workspace.nvim;
|
|
1755
|
+
const entry = this.state.getPackage(name);
|
|
1756
|
+
if (!entry) return;
|
|
1757
|
+
this.detailMode = ["installing", "updating", "uninstalling", "failed"].includes(entry.status) ? "log" : "info";
|
|
1758
|
+
const editorLines = await nvim.call("nvim_get_option", ["lines"]);
|
|
1759
|
+
const editorCols = await nvim.call("nvim_get_option", ["columns"]);
|
|
1760
|
+
const lines = this.buildDetailLines(entry, this.detailMode);
|
|
1761
|
+
const height = this.detailMode === "log" ? 20 : Math.min(lines.length, 20);
|
|
1762
|
+
const row = Math.max(0, Math.floor((editorLines - height - 2) / 2));
|
|
1763
|
+
const col = Math.max(0, Math.floor((editorCols - 82) / 2));
|
|
1764
|
+
const buf = await nvim.createNewBuffer(false, true);
|
|
1765
|
+
this.detailBufnr = buf.id;
|
|
1766
|
+
const win = await nvim.openFloatWindow(buf, true, {
|
|
1767
|
+
relative: "editor",
|
|
1768
|
+
width: 78,
|
|
1769
|
+
height,
|
|
1770
|
+
row,
|
|
1771
|
+
col,
|
|
1772
|
+
border: "rounded",
|
|
1773
|
+
style: "minimal",
|
|
1774
|
+
zindex: 100,
|
|
1775
|
+
title: this.detailMode === "log" ? `${entry.info.displayName} \xB7 Log` : entry.info.displayName,
|
|
1776
|
+
title_pos: "left"
|
|
1777
|
+
});
|
|
1778
|
+
this.detailWinid = win.id;
|
|
1779
|
+
await nvim.call("nvim_win_set_option", [this.detailWinid, "wrap", true]);
|
|
1780
|
+
await nvim.call("nvim_buf_set_option", [this.detailBufnr, "bufhidden", "wipe"]);
|
|
1781
|
+
await nvim.call("nvim_buf_set_option", [this.detailBufnr, "buftype", "nofile"]);
|
|
1782
|
+
const keyBuf = nvim.createBuffer(this.detailBufnr);
|
|
1783
|
+
keyBuf.setKeymap("n", "q", '<Cmd>call CocConverterDispatch("close-detail")<CR>', { silent: true, nowait: true });
|
|
1784
|
+
keyBuf.setKeymap("n", "<Esc>", '<Cmd>call CocConverterDispatch("close-detail")<CR>', { silent: true, nowait: true });
|
|
1785
|
+
await this.updateDetailPopup();
|
|
1786
|
+
}
|
|
1787
|
+
async updateDetailPopup() {
|
|
1788
|
+
if (!this.detailWinid || !this.detailPkgName) return;
|
|
1789
|
+
const entry = this.state.getPackage(this.detailPkgName);
|
|
1790
|
+
if (!entry) return;
|
|
1791
|
+
const lines = this.buildDetailLines(entry, this.detailMode);
|
|
1792
|
+
const nvim = import_coc2.workspace.nvim;
|
|
1793
|
+
await nvim.call("nvim_buf_set_lines", [this.detailBufnr, 0, -1, false, lines]);
|
|
1794
|
+
await nvim.call("nvim_buf_clear_namespace", [this.detailBufnr, this.ns, 0, -1]);
|
|
1795
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1796
|
+
const line = lines[i];
|
|
1797
|
+
if (line.startsWith(" [")) {
|
|
1798
|
+
const endBracket = line.indexOf("]");
|
|
1799
|
+
if (endBracket > 0) {
|
|
1800
|
+
nvim.call("nvim_buf_set_extmark", [this.detailBufnr, this.ns, i, 2, { end_col: endBracket + 1, hl_group: "CocConverterKey" }]);
|
|
1801
|
+
nvim.call("nvim_buf_set_extmark", [this.detailBufnr, this.ns, i, endBracket + 1, { end_col: line.length, hl_group: "Comment" }]);
|
|
1802
|
+
}
|
|
1803
|
+
} else if (line.startsWith(" $ ")) {
|
|
1804
|
+
nvim.call("nvim_buf_set_extmark", [this.detailBufnr, this.ns, i, 0, { end_col: line.length, hl_group: "Comment" }]);
|
|
1805
|
+
} else if (line.includes("\u2717") || line.includes("Error:")) {
|
|
1806
|
+
nvim.call("nvim_buf_set_extmark", [this.detailBufnr, this.ns, i, 0, { end_col: line.length, hl_group: "ErrorMsg" }]);
|
|
1807
|
+
} else if (line.match(/^\s{4}at\s/) || line.match(/^\s{4}Node\.js/)) {
|
|
1808
|
+
nvim.call("nvim_buf_set_extmark", [this.detailBufnr, this.ns, i, 0, { end_col: line.length, hl_group: "Comment" }]);
|
|
1809
|
+
} else if (line.match(/^\s{2}\w+\s{3,}/)) {
|
|
1810
|
+
const parts = line.substring(2).split(/\s{2,}/);
|
|
1811
|
+
if (parts.length >= 2 && ["desc", "type", "status", "source", "langs", "cats", "link", "commit", "server"].includes(parts[0])) {
|
|
1812
|
+
const labelEnd = 2 + parts[0].length + line.substring(2 + parts[0].length).match(/^\s*/)[0].length;
|
|
1813
|
+
nvim.call("nvim_buf_set_extmark", [this.detailBufnr, this.ns, i, 2, { end_col: labelEnd, hl_group: "CocConverterKey" }]);
|
|
1814
|
+
nvim.call("nvim_buf_set_extmark", [this.detailBufnr, this.ns, i, labelEnd, { end_col: line.length, hl_group: "Comment" }]);
|
|
1572
1815
|
}
|
|
1573
|
-
} else {
|
|
1574
|
-
const ln = buf.currentLine();
|
|
1575
|
-
buf.nl(` \u25B6 ${entry.progress}`);
|
|
1576
|
-
logSet.add(ln);
|
|
1577
|
-
pkgLineMap.set(ln, entry.info.name);
|
|
1578
1816
|
}
|
|
1579
1817
|
}
|
|
1580
|
-
if (
|
|
1581
|
-
|
|
1582
|
-
const ln = buf.currentLine();
|
|
1583
|
-
buf.nl(` \u2717 ${entry.error}`);
|
|
1584
|
-
pkgLineMap.set(ln, entry.info.name);
|
|
1818
|
+
if (this.detailMode === "log") {
|
|
1819
|
+
await nvim.call("nvim_win_set_cursor", [this.detailWinid, [lines.length, 0]]);
|
|
1585
1820
|
}
|
|
1586
|
-
|
|
1821
|
+
}
|
|
1822
|
+
closeDetailPopup() {
|
|
1823
|
+
if (!this.detailWinid) return;
|
|
1824
|
+
try {
|
|
1825
|
+
import_coc2.workspace.nvim.call("nvim_win_close", [this.detailWinid, true]);
|
|
1826
|
+
} catch {
|
|
1827
|
+
}
|
|
1828
|
+
this.detailWinid = 0;
|
|
1829
|
+
this.detailBufnr = 0;
|
|
1830
|
+
this.detailPkgName = "";
|
|
1831
|
+
this.detailMode = "info";
|
|
1832
|
+
this.render().catch(() => {
|
|
1833
|
+
});
|
|
1587
1834
|
}
|
|
1588
1835
|
isOpen() {
|
|
1589
1836
|
return this.winid !== 0;
|
|
@@ -1641,7 +1888,7 @@ async function activate(context) {
|
|
|
1641
1888
|
import_coc3.window.showInformationMessage(`${name} is not installed`);
|
|
1642
1889
|
return;
|
|
1643
1890
|
}
|
|
1644
|
-
uninstallPackage(state, name);
|
|
1891
|
+
await uninstallPackage(state, name);
|
|
1645
1892
|
})
|
|
1646
1893
|
);
|
|
1647
1894
|
context.subscriptions.push(
|