coc-vscode-loader 1.2.5 → 1.2.6

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 CHANGED
@@ -37,8 +37,9 @@ 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
- | `gg` | Jump to first package |
41
- | `G` | Jump to last package |
40
+ | `[` / `]` | Previous / next page |
41
+ | `gg` | Jump to first page |
42
+ | `G` | Jump to last page |
42
43
  | `<CR>` | Toggle details (commit / type / source) or install log |
43
44
  | `/` | Search filter |
44
45
  | `q` | Close (auto `:CocRestart` if changes detected) |
@@ -60,6 +61,7 @@ npm install coc-vscode-loader
60
61
 
61
62
  - **Real conversion pipeline** — git clone → converter → npm install → esbuild → register to coc
62
63
  - **Auto-fetch registry** — remote registry fetched in background when TUI opens, no manual refresh needed
64
+ - **Pagination** — `[`/`]` prev/next page, 50 packages per page, handles 5000+ registry entries
63
65
  - **Incremental cache** — source/ keeps git repo, updates via git pull only
64
66
  - **Commit tracking** — records commit SHA after install, visible in detail view
65
67
  - **Update check** — `C` key compares against remote HEAD, shows `↑` when outdated
Binary file
@@ -18,11 +18,25 @@ cd ~/.config/coc/extensions && npm install /path/to/coc-ext
18
18
 
19
19
  ## Verified conversions
20
20
 
21
- | Plugin | Type | Auto-detected | Build | Working | Notes |
22
- |--------|------|---------------|-------|---------|-------|
23
- | Volar (Vue) | TS bridge | `@vue/language-server` + `typescript` | ✅ | ✅ | Requires modified coc-tsserver |
24
- | Prisma | Pure LSP | `@prisma/language-server` | ✅ | ✅ | Auto-detects bin entry |
25
- | HTML CSS Support | Direct API | — | ✅ | ✅ | Handles API differences |
21
+ | Plugin | Type | Notes |
22
+ |--------|------|-------|
23
+ | Volar (Vue) | TS bridge | Requires modified coc-tsserver |
24
+ | Prisma | Pure LSP | Auto-detects bin entry |
25
+ | HTML CSS Support | Direct API | Handles API differences |
26
+ | Deno | Pure LSP | Binary server download |
27
+ | TOML (Taplo) | Pure LSP | Binary server download |
28
+ | Ansible | Pure LSP | npm package server + pip install |
29
+ | YAML | Pure LSP | npm package server |
30
+ | Tailwind CSS | Pure LSP | npm package server, bin entry |
31
+ | Biome | Pure LSP | Binary server download |
32
+ | Stylelint | Pure LSP | npm package server |
33
+ | Prettier | Direct API | Source transforms |
34
+ | Svelte | Pure LSP | npm package server |
35
+ | Astro | Pure LSP | npm package server |
36
+ | Lua | Pure LSP | npm package server |
37
+ | gitignore | Direct API | Source transforms |
38
+
39
+ See the [registry](https://github.com/coc-plugin/coc-vscode-registry) for the full list and latest status.
26
40
 
27
41
  ### Plugin types
28
42
 
@@ -77,21 +91,21 @@ const PRESETS = {
77
91
  `convert.ts` only calls `getActivePresets()` + `generateBridgeCode()`, it never touches bridge logic directly.
78
92
  Adding a new bridge type = add a new preset in `presets.ts`, no changes to main flow.
79
93
 
80
- See [coc-vscode-registry/docs/converter-design-v2.md](https://github.com/coc-plugin/coc-vscode-registry/blob/main/docs/converter-design-v2.md).
94
+ See [../docs/converter-design-v2.md](../docs/converter-design-v2.md).
81
95
 
82
96
  ## File structure
83
97
 
84
98
  | File | Lines | Description |
85
99
  |------|-------|-------------|
86
- | `src/cli.ts` | 28 | CLI entry |
87
- | `src/convert.ts` | 484 | Main flow + template generation + API replacement |
88
- | `src/scanner.ts` | 136 | API scanner + plugin classification |
89
- | `src/transforms/import-mapping.ts` | 47 | Import replacement |
100
+ | `src/cli.ts` | 59 | CLI entry |
101
+ | `src/convert.ts` | 461 | Main flow + template generation + API replacement |
102
+ | `src/scanner.ts` | 52 | API scanner + plugin classification |
103
+ | `src/transforms/import-mapping.ts` | 193 | Import replacement |
90
104
  | `src/transforms/language-client.ts` | 48 | LanguageClient adaptation |
91
- | `src/transforms/class-to-factory.ts` | 54 | new Xxx() → Xxx.create() |
92
- | `src/transforms/provider-register.ts` | 55 | Provider registration signature fixes |
93
- | `src/transforms/enum-offset.ts` | 49 | Enum value offset annotations |
94
- | **Total** | **~870** | |
105
+ | `src/transforms/class-to-factory.ts` | 53 | new Xxx() → Xxx.create() |
106
+ | `src/transforms/provider-register.ts` | 61 | Provider registration signature fixes |
107
+ | `src/transforms/enum-offset.ts` | 32 | Enum value offset annotations |
108
+ | **Total** | **~959** | |
95
109
 
96
110
  ## Handled API differences
97
111
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "converter",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "converter",
9
- "version": "1.2.5",
9
+ "version": "1.2.6",
10
10
  "dependencies": {
11
11
  "commander": "^15.0.0",
12
12
  "ts-morph": "^28.0.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "converter",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "private": true,
5
5
  "description": "vscode → coc.nvim converter prototype",
6
6
  "type": "module",
@@ -326,6 +326,7 @@ export async function convert(opts: ConvertOptions): Promise<void> {
326
326
  command: c.command,
327
327
  title: c.title,
328
328
  })) || undefined,
329
+ snippets: origPkg.contributes?.snippets || undefined,
329
330
  ...(tsPlugins.length > 0 ? {
330
331
  typescriptServerPlugins: tsPlugins.map(p => ({
331
332
  ...p,
@@ -338,6 +339,7 @@ export async function convert(opts: ConvertOptions): Promise<void> {
338
339
  // Clean null fields
339
340
  if (!pkg.contributes?.configuration) delete (pkg.contributes as any).configuration
340
341
  if (!pkg.contributes?.commands) delete (pkg.contributes as any).commands
342
+ if (!pkg.contributes?.snippets) delete (pkg.contributes as any).snippets
341
343
  if (Object.keys(pkg.contributes).length === 0) delete (pkg as any).contributes
342
344
 
343
345
  fs.writeFileSync(path.join(output, 'package.json'), JSON.stringify(pkg, null, 2))
@@ -3,6 +3,7 @@ import { languageClientGenerator } from './language-client.js'
3
3
  import { sourceGenerator } from './source.js'
4
4
  import { bridgeGenerator } from './bridge.js'
5
5
  import { markUnsupportedGenerator } from './mark-unsupported.js'
6
+ import { snippetsGenerator } from './snippets.js'
6
7
 
7
8
  const REGISTRY: Record<string, StepGenerator> = {}
8
9
 
@@ -27,3 +28,4 @@ registerGenerator(languageClientGenerator)
27
28
  registerGenerator(sourceGenerator)
28
29
  registerGenerator(bridgeGenerator)
29
30
  registerGenerator(markUnsupportedGenerator)
31
+ registerGenerator(snippetsGenerator)
@@ -0,0 +1,101 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+ import { StepGenerator, StepContext, SnippetsStep, StepResult } from '../types.js'
4
+
5
+ export const snippetsGenerator: StepGenerator = {
6
+ type: 'snippets',
7
+
8
+ generate(ctx: StepContext, step: any): StepResult {
9
+ const ss = step as SnippetsStep
10
+ const { input, output, origPkg, verbose } = ctx
11
+
12
+ const contributedSnippets: Array<{ language: string; path: string }> =
13
+ origPkg.contributes?.snippets || []
14
+
15
+ if (contributedSnippets.length === 0 && !ss.languages) {
16
+ throw new Error('snippets step: source package.json has no contributes.snippets, and no languages specified in step config')
17
+ }
18
+
19
+ // Collect unique (sourcePath → [languages]) mappings
20
+ const fileToLanguages = new Map<string, string[]>()
21
+ if (ss.languages) {
22
+ for (const lang of ss.languages) {
23
+ const entry = contributedSnippets.find(s => s.language === lang)
24
+ if (entry) {
25
+ const langs = fileToLanguages.get(entry.path) || []
26
+ langs.push(lang)
27
+ fileToLanguages.set(entry.path, langs)
28
+ } else {
29
+ const defaultPath = `./snippets/${lang}.json`
30
+ const fp = path.join(input, defaultPath)
31
+ if (fs.existsSync(fp)) {
32
+ const langs = fileToLanguages.get(defaultPath) || []
33
+ langs.push(lang)
34
+ fileToLanguages.set(defaultPath, langs)
35
+ } else if (verbose) {
36
+ console.warn(` snippets: no snippet file found for language "${lang}", skipping`)
37
+ }
38
+ }
39
+ }
40
+ } else {
41
+ for (const s of contributedSnippets) {
42
+ const langs = fileToLanguages.get(s.path) || []
43
+ langs.push(s.language)
44
+ fileToLanguages.set(s.path, langs)
45
+ }
46
+ }
47
+
48
+ if (fileToLanguages.size === 0) {
49
+ throw new Error('snippets step: no snippet files found to copy')
50
+ }
51
+
52
+ // Create output directories and copy files to original relative paths
53
+ const srcDir = path.join(output, 'src')
54
+ fs.mkdirSync(srcDir, { recursive: true })
55
+
56
+ const generatedFiles: Array<{ path: string; content: string }> = []
57
+ let copiedCount = 0
58
+ const allLanguages: string[] = []
59
+
60
+ for (const [sourceRelPath, languages] of fileToLanguages) {
61
+ const sourceFile = path.join(input, sourceRelPath)
62
+ if (!fs.existsSync(sourceFile)) {
63
+ if (verbose) console.warn(` snippets: source file not found: ${sourceFile}, skipping`)
64
+ continue
65
+ }
66
+ const dest = path.join(output, sourceRelPath)
67
+ fs.mkdirSync(path.dirname(dest), { recursive: true })
68
+ fs.copyFileSync(sourceFile, dest)
69
+ copiedCount++
70
+ allLanguages.push(...languages)
71
+ if (verbose) console.log(` snippets: copied ${sourceRelPath} (${languages.join(', ')})`)
72
+ }
73
+
74
+ if (copiedCount === 0) {
75
+ throw new Error('snippets step: no snippet files were copied')
76
+ }
77
+
78
+ // Generate empty src/index.ts
79
+ const indexContent = `\
80
+ import { ExtensionContext } from 'coc.nvim'
81
+
82
+ export function activate(context: ExtensionContext): void {
83
+ // coc-snippets discovers snippets via package.json's contributes.snippets
84
+ }
85
+ `
86
+ generatedFiles.push({ path: 'src/index.ts', content: indexContent })
87
+
88
+ const activationEvents = [...new Set(allLanguages)].map(l => `onLanguage:${l}`)
89
+
90
+ if (verbose) {
91
+ console.log(` snippets: ${copiedCount} files, ${new Set(allLanguages).size} languages`)
92
+ }
93
+
94
+ return {
95
+ generatedFiles,
96
+ entryPoint: 'src/index.ts',
97
+ keepDeps: {},
98
+ activationEvents,
99
+ }
100
+ },
101
+ }
@@ -73,7 +73,13 @@ export interface MarkUnsupportedStep {
73
73
  verbose?: boolean
74
74
  }
75
75
 
76
- export type ConvertStep = LanguageClientStep | SourceStep | BridgeStep | MarkUnsupportedStep
76
+ export interface SnippetsStep {
77
+ type: 'snippets'
78
+ /** Optional: override languages to generate (default: read from source package.json's contributes.snippets) */
79
+ languages?: string[]
80
+ }
81
+
82
+ export type ConvertStep = LanguageClientStep | SourceStep | BridgeStep | MarkUnsupportedStep | SnippetsStep
77
83
 
78
84
  // ---- Step execution ----
79
85
 
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.5",
42
+ version: "1.2.6",
43
43
  description: "Run VS Code extensions seamlessly in coc.nvim",
44
44
  main: "lib/index.js",
45
45
  keywords: [
@@ -180,7 +180,7 @@ async function fetchRegistryJSON(url) {
180
180
  } catch {
181
181
  }
182
182
  return new Promise((resolve2, reject) => {
183
- (0, import_child_process.execFile)("curl", ["-sL", url], { encoding: "utf-8", maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
183
+ (0, import_child_process.execFile)("curl", ["-sL", "--compressed", url], { encoding: "utf-8", maxBuffer: 20 * 1024 * 1024 }, (err, stdout) => {
184
184
  if (err) reject(new Error(`curl failed: ${err.message}`));
185
185
  else {
186
186
  try {
@@ -239,6 +239,7 @@ var os2 = __toESM(require("os"));
239
239
  function isInstalled(name) {
240
240
  return fs2.existsSync(path2.join(os2.homedir(), ".config", "coc", "extensions", "node_modules", `coc-${name}`));
241
241
  }
242
+ var PAGE_SIZE = 50;
242
243
  function createInitialState() {
243
244
  const packages = getAllPackages().map((info) => {
244
245
  const installed = isInstalled(info.name);
@@ -268,7 +269,7 @@ function createInitialState() {
268
269
  marked: false
269
270
  };
270
271
  });
271
- return { packages, searchQuery: "", showHelp: false, activePill: null, dirty: false, viewFilter: "all", sortBy: "default" };
272
+ return { packages, searchQuery: "", showHelp: false, activePill: null, dirty: false, viewFilter: "all", sortBy: "default", currentPage: 0 };
272
273
  }
273
274
  var StateManager = class {
274
275
  constructor(initial) {
@@ -339,21 +340,25 @@ var StateManager = class {
339
340
  setViewFilter(filter) {
340
341
  this.mutate((s) => {
341
342
  s.viewFilter = filter;
343
+ s.currentPage = 0;
342
344
  });
343
345
  }
344
346
  cycleViewFilter() {
345
347
  this.mutate((s) => {
346
348
  s.viewFilter = s.viewFilter === "all" ? "installed" : s.viewFilter === "installed" ? "not-installed" : "all";
349
+ s.currentPage = 0;
347
350
  });
348
351
  }
349
352
  setSortBy(sortBy) {
350
353
  this.mutate((s) => {
351
354
  s.sortBy = sortBy;
355
+ s.currentPage = 0;
352
356
  });
353
357
  }
354
358
  cycleSortBy() {
355
359
  this.mutate((s) => {
356
360
  s.sortBy = s.sortBy === "default" ? "name" : s.sortBy === "name" ? "status" : s.sortBy === "status" ? "type" : "default";
361
+ s.currentPage = 0;
357
362
  });
358
363
  }
359
364
  setStatusMessage(msg) {
@@ -374,6 +379,12 @@ var StateManager = class {
374
379
  setSearchQuery(query) {
375
380
  this.mutate((s) => {
376
381
  s.searchQuery = query;
382
+ s.currentPage = 0;
383
+ });
384
+ }
385
+ setPage(n) {
386
+ this.mutate((s) => {
387
+ s.currentPage = n;
377
388
  });
378
389
  }
379
390
  getFilteredPackages() {
@@ -1028,8 +1039,9 @@ var HELP_TEXT = [
1028
1039
  " x Toggle mark",
1029
1040
  " f Cycle filter: all \u2192 installed \u2192 not-installed",
1030
1041
  " s Cycle sort: default \u2192 name \u2192 status \u2192 type",
1031
- " gg Jump to first package",
1032
- " G Jump to last package",
1042
+ " [/] Previous/next page",
1043
+ " gg Jump to first page",
1044
+ " G Jump to last page",
1033
1045
  " <Enter> Toggle expand/collapse details",
1034
1046
  " / Search filter",
1035
1047
  " q Close window",
@@ -1068,10 +1080,14 @@ var TUI = class {
1068
1080
  x: "x",
1069
1081
  D: "D",
1070
1082
  gg: "gg",
1071
- G: "G"
1083
+ G: "G",
1084
+ "[": "pageup",
1085
+ "]": "pagedown"
1072
1086
  };
1073
1087
  this.rendering = false;
1074
1088
  this.pendingRender = false;
1089
+ this.lastPage = -1;
1090
+ this.scrollToFirst = false;
1075
1091
  this.state = state;
1076
1092
  }
1077
1093
  async open() {
@@ -1219,16 +1235,29 @@ var TUI = class {
1219
1235
  return;
1220
1236
  }
1221
1237
  if (id === "gg") {
1222
- const firstLine = Math.min(...this.pkgLineMap.keys());
1223
- if (isFinite(firstLine)) {
1224
- await import_coc2.workspace.nvim.call("nvim_win_set_cursor", [this.winid, [firstLine + 1, 0]]);
1225
- }
1238
+ this.state.setPage(0);
1239
+ this.scrollToFirst = true;
1226
1240
  return;
1227
1241
  }
1228
1242
  if (id === "G") {
1229
- const lastLine = Math.max(...this.pkgLineMap.keys());
1230
- if (isFinite(lastLine)) {
1231
- await import_coc2.workspace.nvim.call("nvim_win_set_cursor", [this.winid, [lastLine + 1, 0]]);
1243
+ const filtered = this.state.getFilteredPackages();
1244
+ const totalPages = Math.ceil(filtered.length / PAGE_SIZE) || 1;
1245
+ this.state.setPage(totalPages - 1);
1246
+ return;
1247
+ }
1248
+ if (id === "pagedown") {
1249
+ const filtered = this.state.getFilteredPackages();
1250
+ const totalPages = Math.ceil(filtered.length / PAGE_SIZE) || 1;
1251
+ const s2 = this.state.getState();
1252
+ if (s2.currentPage < totalPages - 1) {
1253
+ this.state.setPage(s2.currentPage + 1);
1254
+ }
1255
+ return;
1256
+ }
1257
+ if (id === "pageup") {
1258
+ const s2 = this.state.getState();
1259
+ if (s2.currentPage > 0) {
1260
+ this.state.setPage(s2.currentPage - 1);
1232
1261
  }
1233
1262
  return;
1234
1263
  }
@@ -1325,6 +1354,8 @@ var TUI = class {
1325
1354
  ["D", "D"],
1326
1355
  ["gg", "gg"],
1327
1356
  ["G", "G"],
1357
+ ["[", "pageup"],
1358
+ ["]", "pagedown"],
1328
1359
  ["<CR>", "cr"]
1329
1360
  ];
1330
1361
  for (const [vimKey, id] of entries) {
@@ -1378,6 +1409,15 @@ var TUI = class {
1378
1409
  }], true);
1379
1410
  }
1380
1411
  await nvim.resumeNotification();
1412
+ const shouldScroll = this.scrollToFirst || state.currentPage !== this.lastPage;
1413
+ this.scrollToFirst = false;
1414
+ this.lastPage = state.currentPage;
1415
+ if (!state.showHelp && shouldScroll) {
1416
+ const firstPkgLine = Math.min(...result.pkgLineMap.keys());
1417
+ if (isFinite(firstPkgLine)) {
1418
+ await nvim.call("nvim_win_set_cursor", [this.winid, [firstPkgLine + 1, 0]]);
1419
+ }
1420
+ }
1381
1421
  this.pkgLineMap = result.pkgLineMap;
1382
1422
  this.logLineSet = result.logLines;
1383
1423
  } finally {
@@ -1443,26 +1483,29 @@ var TUI = class {
1443
1483
  }
1444
1484
  buf.nl();
1445
1485
  buf.nl();
1446
- const installed = filtered.filter((e) => e.status !== "not-installed");
1447
- const available = filtered.filter((e) => e.status === "not-installed");
1448
- const section = (title, entries) => {
1449
- if (entries.length === 0) return;
1450
- buf.nl(`${title}`);
1451
- for (const e of entries) {
1452
- this.renderEntry(buf, pkgLineMap, logSet, e);
1453
- }
1454
- buf.nl();
1455
- buf.nl();
1456
- };
1457
- section(`Installed (${installed.length})`, installed);
1458
- section(`Available (${available.length})`, available);
1486
+ const totalPages = Math.ceil(filtered.length / PAGE_SIZE) || 1;
1487
+ const page = Math.min(state.currentPage, totalPages - 1);
1488
+ const start = page * PAGE_SIZE;
1489
+ const pageItems = filtered.slice(start, start + PAGE_SIZE);
1490
+ const end = Math.min(start + PAGE_SIZE, filtered.length);
1491
+ if (totalPages > 1) {
1492
+ buf.append(`Page ${page + 1}/${totalPages} \xB7 ${start + 1}\u2013${end} of ${filtered.length}`, "CocConverterTotal");
1493
+ } else {
1494
+ buf.append(`${filtered.length} packages`, "CocConverterTotal");
1495
+ }
1496
+ buf.nl();
1497
+ buf.nl();
1498
+ for (const e of pageItems) {
1499
+ this.renderEntry(buf, pkgLineMap, logSet, e);
1500
+ }
1459
1501
  if (filtered.length === 0 && state.searchQuery) {
1460
1502
  buf.nl("no matching packages");
1461
1503
  }
1462
1504
  buf.nl();
1463
1505
  buf.append(" " + "\u2500".repeat(50), "Comment");
1464
1506
  buf.nl();
1465
- buf.append(` ${filtered.length} packages \xB7 ${filterLabel} \xB7 ${sortLabel} order`, "Comment");
1507
+ const pageNav = totalPages > 1 ? ` [ prev ] next` : "";
1508
+ buf.append(` ${filtered.length} packages \xB7 ${filterLabel} \xB7 ${sortLabel}${pageNav}`, "Comment");
1466
1509
  const result = buf.render(2);
1467
1510
  return { lines: result.lines, pkgLineMap, logLines: logSet, highlights: result.highlights };
1468
1511
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coc-vscode-loader",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "Run VS Code extensions seamlessly in coc.nvim",
5
5
  "main": "lib/index.js",
6
6
  "keywords": [