coc-vscode-loader 1.1.9 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,8 +39,7 @@ export const transformClassToFactory: Transform = (ctx) => {
39
39
  (match, type) => `${type}.create(`
40
40
  )
41
41
 
42
- // CompletionItem.create(label) doesn't accept kind in coc.
43
- // Convert `CompletionItem.create(label, kind)` to `item = CompletionItem.create(label); item.kind = kind`
42
+ // CompletionItem.create(label, kind) item = CompletionItem.create(label); item.kind = kind
44
43
  text = text.replace(
45
44
  /const\s+(\w+)\s*=\s*CompletionItem\.create\(([^,]+),\s*([^)]+)\)/g,
46
45
  (_, varName, label, kind) => {
@@ -19,23 +19,6 @@ export const transformEnumOffset: Transform = (ctx) => {
19
19
  const { file } = ctx
20
20
  let content = file.getText()
21
21
 
22
- // Detect hardcoded numbers used in enum position (e.g., CompletionItemKind.Xxx).
23
- // This is hard to detect perfectly, so we log a note when numeric literals
24
- // appear near enum-type names.
25
- const enumPatterns = [
26
- 'CompletionItemKind', 'SymbolKind', 'DocumentHighlightKind', 'DiagnosticSeverity',
27
- 'CompletionTriggerKind', 'InlineCompletionTriggerKind',
28
- ]
29
-
30
- for (const enumName of enumPatterns) {
31
- // Check if the enum is imported/used with a hardcoded number nearby
32
- const enumRefs = content.match(new RegExp(`${enumName}\\.\\w+`, 'g'))
33
- if (enumRefs) {
34
- // Symbol references are fine - they resolve at runtime
35
- // Only note if there are raw numbers being compared
36
- }
37
- }
38
-
39
22
  // Replace any numeric enum comparisons with comments
40
23
  // e.g., `severity === 0` → `severity === 0 /* DiagnosticSeverity.Error = 1 in coc */`
41
24
  content = content.replace(
@@ -1,3 +1,4 @@
1
+ import * as path from 'path'
1
2
  import { Transform } from '../types.js'
2
3
 
3
4
  /**
@@ -32,10 +33,15 @@ export const transformProviderRegister: Transform = (ctx) => {
32
33
  }
33
34
 
34
35
  // 2. registerCompletionItemProvider: insert name + shortcut at beginning
36
+ // Shortcut derived from source file name (first 2 uppercase letters)
37
+ const fileName = file.getFilePath() || 'plugin'
38
+ const baseName = path.basename(fileName, '.ts')
39
+ const shortcut = baseName.replace(/[^a-zA-Z]/g, '').substring(0, 2).toUpperCase() || 'PL'
35
40
  if (content.includes('registerCompletionItemProvider')) {
41
+ const pluginName = path.basename(path.dirname(path.dirname(fileName))) || 'plugin'
36
42
  content = content.replace(
37
43
  /registerCompletionItemProvider\(/g,
38
- `registerCompletionItemProvider('plugin', 'PL', `
44
+ `registerCompletionItemProvider('${pluginName}', '${shortcut}', `
39
45
  )
40
46
  // Wrap the last argument in an array if it's a string (trigger chars)
41
47
  content = content.replace(
@@ -0,0 +1,29 @@
1
+ import { Transform } from '../types.js'
2
+
3
+ /**
4
+ * Remove Volar-specific framework imports that are not compatible with coc.nvim.
5
+ * The Volar extension uses @volar/vscode and reactive-vscode meta-framework,
6
+ * which need to be stripped since the bridge step provides alternative setup.
7
+ */
8
+ export const transformStripVolar: Transform = (ctx) => {
9
+ const { file } = ctx
10
+ let content = file.getText()
11
+ let changed = false
12
+
13
+ const patterns = [
14
+ /import .* from ['"]@volar\/vscode['"];?\n?/g,
15
+ /import .* from ['"]reactive-vscode['"];?\n?/g,
16
+ /import \* as lsp from ['"]@volar\/vscode\/node['"];?\n?/g,
17
+ ]
18
+
19
+ for (const re of patterns) {
20
+ if (re.test(content)) {
21
+ content = content.replace(re, '')
22
+ changed = true
23
+ }
24
+ }
25
+
26
+ if (changed) {
27
+ file.replaceWithText(content)
28
+ }
29
+ }
@@ -6,3 +6,120 @@ export interface TransformContext {
6
6
  }
7
7
 
8
8
  export type Transform = (ctx: TransformContext) => void
9
+
10
+ // ---- Step type definitions ----
11
+
12
+ export interface ServerModuleConfig {
13
+ kind: 'module'
14
+ package: string
15
+ entry?: 'main' | 'bin'
16
+ }
17
+
18
+ export interface ServerBinaryConfig {
19
+ kind: 'binary'
20
+ package: string
21
+ binary: {
22
+ repo: string
23
+ asset: string
24
+ binaryPath?: string
25
+ }
26
+ args?: string[]
27
+ }
28
+
29
+ export type ServerConfig = ServerModuleConfig | ServerBinaryConfig
30
+
31
+ export interface LanguageClientStep {
32
+ type: 'language-client'
33
+ id?: string
34
+ server: ServerConfig
35
+ transport?: 'ipc' | 'stdio'
36
+ languages: string[]
37
+ multiRoot?: boolean
38
+ /** Enable debug logging in generated code */
39
+ verbose?: boolean
40
+ }
41
+
42
+ export interface SourceStep {
43
+ type: 'source'
44
+ transforms: string[]
45
+ entry?: string
46
+ keepDeps?: string[] | Record<string, string>
47
+ activationEvents?: string[]
48
+ /** Enable debug logging in generated/transformed code */
49
+ verbose?: boolean
50
+ }
51
+
52
+ export interface BridgeStep {
53
+ type: 'bridge'
54
+ preset?: string
55
+ /** Override preset options (extensions, services, etc.) */
56
+ options?: {
57
+ extensions?: string[]
58
+ services?: string[]
59
+ }
60
+ /** Enable debug logging in generated bridge code */
61
+ verbose?: boolean
62
+ }
63
+
64
+ export interface MarkUnsupportedStep {
65
+ type: 'mark-unsupported'
66
+ features: string[]
67
+ /** Enable detailed output during conversion */
68
+ verbose?: boolean
69
+ }
70
+
71
+ export type ConvertStep = LanguageClientStep | SourceStep | BridgeStep | MarkUnsupportedStep
72
+
73
+ // ---- Step execution ----
74
+
75
+ export interface StepResult {
76
+ generatedFiles: Array<{ path: string; content: string }>
77
+ entryPoint?: string
78
+ keepDeps: Record<string, string>
79
+ activationEvents: string[]
80
+ serverBinary?: {
81
+ repo: string
82
+ asset: string
83
+ binaryPath?: string
84
+ args?: string[]
85
+ }
86
+ /** Code to inject into previously generated files (target path, code to insert, insertion point) */
87
+ codeInjections?: Array<{
88
+ target: string // file to modify (e.g. 'src/index.ts')
89
+ importCode?: string // import line to add at top
90
+ insertBefore?: string // regex pattern to insert code before
91
+ insertAfter?: string // regex pattern to insert code after
92
+ code: string // code to insert
93
+ }>
94
+ }
95
+
96
+ export interface StepContext {
97
+ input: string
98
+ output: string
99
+ project: Project
100
+ origPkg: Record<string, any>
101
+ verbose?: boolean
102
+ /** Preset definitions from registry (e.g. bridge presets) */
103
+ presets?: Record<string, any>
104
+ }
105
+
106
+ export interface StepGenerator {
107
+ type: string
108
+ generate(ctx: StepContext, step: ConvertStep): StepResult
109
+ }
110
+
111
+ export function isLanguageClientStep(s: ConvertStep): s is LanguageClientStep {
112
+ return s.type === 'language-client'
113
+ }
114
+
115
+ export function isSourceStep(s: ConvertStep): s is SourceStep {
116
+ return s.type === 'source'
117
+ }
118
+
119
+ export function isBridgeStep(s: ConvertStep): s is BridgeStep {
120
+ return s.type === 'bridge'
121
+ }
122
+
123
+ export function isMarkUnsupportedStep(s: ConvertStep): s is MarkUnsupportedStep {
124
+ return s.type === 'mark-unsupported'
125
+ }
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.1.9",
42
+ version: "1.2.1",
43
43
  description: "Run VS Code extensions seamlessly in coc.nvim",
44
44
  main: "lib/index.js",
45
45
  keywords: [
@@ -159,9 +159,13 @@ async function fetchRegistryJSON(url) {
159
159
  (0, import_child_process.execFile)("curl", ["-sL", url], { encoding: "utf-8", maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
160
160
  if (err) reject(new Error(`curl failed: ${err.message}`));
161
161
  else {
162
- const data = JSON.parse(stdout);
163
- if (!Array.isArray(data)) reject(new Error("Invalid registry format"));
164
- else resolve2(data);
162
+ try {
163
+ const data = JSON.parse(stdout);
164
+ if (!Array.isArray(data)) reject(new Error("Invalid registry format"));
165
+ else resolve2(data);
166
+ } catch (e) {
167
+ reject(new Error(`Invalid JSON from registry: ${e.message}`));
168
+ }
165
169
  }
166
170
  });
167
171
  });
@@ -437,8 +441,10 @@ function pluginDir(name) {
437
441
  function converterCliPath() {
438
442
  const base = path3.resolve(__dirname, "..");
439
443
  const candidates = [
440
- path3.join(base, "converter", "src", "cli.ts"),
441
- path3.join(base, "..", "converter", "src", "cli.ts")
444
+ // In dev mode (symlink), the converter is at repo root
445
+ path3.join(base, "..", "converter", "src", "cli.ts"),
446
+ // In npm install, the converter is bundled inside the package
447
+ path3.join(base, "converter", "src", "cli.ts")
442
448
  ];
443
449
  for (const p of candidates) {
444
450
  if (fs3.existsSync(p)) return p;
@@ -450,7 +456,7 @@ function converterCliPath() {
450
456
  var CMD_TIMEOUT = 3e5;
451
457
  async function run(cmd, args, cwd, onLine) {
452
458
  return new Promise((resolve2, reject) => {
453
- const child = (0, import_child_process2.spawn)(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: true });
459
+ const child = (0, import_child_process2.spawn)(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
454
460
  const timer = setTimeout(() => {
455
461
  child.kill("SIGTERM");
456
462
  reject(new Error(`Timed out after ${CMD_TIMEOUT / 1e3}s: ${cmd} ${args.join(" ")}`));
@@ -492,7 +498,7 @@ async function downloadSource(info, name, onProgress) {
492
498
  }
493
499
  return info.source.subdir ? path3.join(srcDir, info.source.subdir) : srcDir;
494
500
  }
495
- async function convertSource(inputDir, name, onProgress) {
501
+ async function convertSource(inputDir, name, info, onProgress) {
496
502
  const build = buildDir(name);
497
503
  if (fs3.existsSync(build)) fs3.rmSync(build, { recursive: true });
498
504
  const cli = converterCliPath();
@@ -500,11 +506,40 @@ async function convertSource(inputDir, name, onProgress) {
500
506
  if (!fs3.existsSync(path3.join(converterDir, "node_modules", "commander"))) {
501
507
  onProgress(2, 5, "Installing converter dependencies...", "");
502
508
  const log2 = (chunk) => onProgress(2, 5, chunk.trim(), "");
503
- await run("npm", ["install", "--legacy-peer-deps", "--production"], converterDir, log2);
509
+ await run("npm", ["install", "--legacy-peer-deps", "--omit=dev"], converterDir, log2);
510
+ }
511
+ if (!info.convert || !Array.isArray(info.convert) || info.convert.length === 0) {
512
+ throw new Error(`Registry entry "${name}" has no "convert" config. Please update the registry.`);
513
+ }
514
+ const convertFile = path3.join(cacheDir(name), "convert-config.json");
515
+ fs3.mkdirSync(path3.dirname(convertFile), { recursive: true });
516
+ fs3.writeFileSync(convertFile, JSON.stringify(info.convert));
517
+ const presetsFile = path3.join(cacheDir(name), "presets-config.json");
518
+ const registryDir = path3.resolve(__dirname, "..", "..", "coc-vscode-registry");
519
+ const localPresets = path3.join(registryDir, "presets.json");
520
+ if (fs3.existsSync(localPresets)) {
521
+ fs3.writeFileSync(presetsFile, fs3.readFileSync(localPresets));
522
+ } else {
523
+ const globalPresetsCache = path3.join(os3.homedir(), ".config", "coc", "converter-cache", "presets.json");
524
+ if (!fs3.existsSync(globalPresetsCache)) {
525
+ try {
526
+ const res = await fetch("https://raw.githubusercontent.com/coc-plugin/coc-vscode-registry/main/presets.json");
527
+ if (res.ok) {
528
+ fs3.mkdirSync(path3.dirname(globalPresetsCache), { recursive: true });
529
+ fs3.writeFileSync(globalPresetsCache, await res.text());
530
+ }
531
+ } catch {
532
+ }
533
+ }
534
+ if (fs3.existsSync(globalPresetsCache)) {
535
+ fs3.writeFileSync(presetsFile, fs3.readFileSync(globalPresetsCache));
536
+ }
504
537
  }
538
+ const args = ["tsx", cli, "convert", inputDir, "-o", build, "--convert-file", convertFile];
539
+ if (fs3.existsSync(presetsFile)) args.push("--presets-file", presetsFile);
505
540
  onProgress(2, 5, "Converting...", `converter convert ${inputDir} -o ${build}`);
506
541
  const log = (chunk) => onProgress(2, 5, chunk.trim(), "");
507
- await run("npx", ["tsx", cli, "convert", inputDir, "-o", build], cacheDir(name), log);
542
+ await run("npx", args, cacheDir(name), log);
508
543
  }
509
544
  async function buildPackage(name, inputDir, info, onProgress) {
510
545
  const build = buildDir(name);
@@ -614,56 +649,18 @@ async function buildPackage(name, inputDir, info, onProgress) {
614
649
  }
615
650
  const indexPath = path3.join(build, "lib", "index.js");
616
651
  if (fs3.existsSync(indexPath)) {
617
- const binPath = (sb.binaryPath || sb.asset.split(/-?\{\{/)[0]).replace(/\{\{version}}/g, version).replace(/\{\{platform}}/g, platform).replace(/\{\{arch}}/g, arch2).replace(/\{\{raw-arch}}/g, rawArch).replace(/\{\{rust-target}}/g, rustTarget);
618
652
  let code = fs3.readFileSync(indexPath, "utf-8");
619
- const svrArgs = sb.args?.length ? JSON.stringify(sb.args) : "[]";
620
- code = code.replace(
621
- /\{ module:\s*serverModule,\s*transport:\s*\w+\.TransportKind\.\w+\s*\}/,
622
- `{ command: serverModule, args: ${svrArgs} }`
623
- );
624
- const serverPath = `require('path').join(__dirname, '..', 'server', '${binPath}')`;
625
- code = code.replace(
626
- /try\s*\{[^}]*?require\.resolve\([^)]+\)\s*;?\s*\}\s*catch\s*\{\s*\}/g,
627
- `try { serverModule = ${serverPath} } catch {}`
628
- );
629
- code = code.replace(
630
- /let\s+serverModule\s*=\s*config\.get\([^)]+\)\s*;?\s*/g,
631
- `let serverModule = ${serverPath};`
632
- );
653
+ code = code.replace(/\{\{version}}/g, version);
654
+ code = code.replace(/\{\{platform}}/g, platform);
655
+ code = code.replace(/\{\{arch}}/g, arch2);
656
+ code = code.replace(/\{\{raw-arch}}/g, rawArch);
657
+ code = code.replace(/\{\{rust-target}}/g, rustTarget);
633
658
  fs3.writeFileSync(indexPath, code);
634
659
  }
635
660
  } catch (e) {
636
661
  onProgress(4, 5, `Warning: serverBinary setup failed (${e.message})`, "install server binary manually");
637
662
  }
638
663
  }
639
- const docSelPath = path3.join(build, "lib", "index.js");
640
- if (fs3.existsSync(docSelPath)) {
641
- let code = fs3.readFileSync(docSelPath, "utf-8");
642
- const langSelector = info.languages.map((l) => `{ scheme: "file", language: "${l}" }`).join(", ");
643
- code = code.replace(
644
- /documentSelector:\s*\[\s*\{[^}]*?language:\s*['"][^'"]*['"][^}]*\}\s*\]/,
645
- `documentSelector: [${langSelector}]`
646
- );
647
- code = code.replace(
648
- /client\.start\(\);/g,
649
- "client.start().catch(() => {/* init may complete async */});"
650
- );
651
- fs3.writeFileSync(docSelPath, code);
652
- }
653
- const pkgPath = path3.join(build, "package.json");
654
- if (fs3.existsSync(pkgPath)) {
655
- try {
656
- const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
657
- const events = pkg.activationEvents || [];
658
- const langEvents = info.languages.map((l) => `onLanguage:${l}`);
659
- const newEvents = events.filter((e) => !e.startsWith("onLanguage:")).concat(langEvents);
660
- if (newEvents.length > 0 && JSON.stringify(newEvents) !== JSON.stringify(events)) {
661
- pkg.activationEvents = newEvents;
662
- fs3.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
663
- }
664
- } catch {
665
- }
666
- }
667
664
  }
668
665
  function extensionsPkgPath() {
669
666
  return path3.join(os3.homedir(), ".config", "coc", "extensions", "package.json");
@@ -676,7 +673,7 @@ async function installToCoc(name, onProgress) {
676
673
  fs3.mkdirSync(path3.dirname(dest), { recursive: true });
677
674
  fs3.cpSync(src, dest, { recursive: true });
678
675
  const pkgPath = extensionsPkgPath();
679
- const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
676
+ const pkg = fs3.existsSync(pkgPath) ? JSON.parse(fs3.readFileSync(pkgPath, "utf-8")) : { dependencies: {} };
680
677
  pkg.dependencies = pkg.dependencies || {};
681
678
  const depName = `coc-${name}`;
682
679
  if (!pkg.dependencies[depName]) {
@@ -714,7 +711,7 @@ async function installPackage(state, name) {
714
711
  state.setPackageStatus(name, "installing", { progress: "Starting..." });
715
712
  try {
716
713
  const input = await downloadSource(info, name, prog);
717
- await convertSource(input, name, prog);
714
+ await convertSource(input, name, info, prog);
718
715
  await buildPackage(name, input, info, prog);
719
716
  await installToCoc(name, prog);
720
717
  await saveMeta(name);
@@ -786,13 +783,13 @@ async function updatePackage(state, name) {
786
783
  state.setPackageStatus(name, "updating", { progress: "Starting..." });
787
784
  try {
788
785
  const input = await downloadSource(info, name, prog);
789
- await convertSource(input, name, prog);
786
+ await convertSource(input, name, info, prog);
790
787
  await buildPackage(name, input, info, prog);
791
788
  await installToCoc(name, prog);
792
789
  await saveMeta(name);
793
790
  state.setDirty();
794
791
  state.setPackageStatus(name, "installed");
795
- import_coc.window.showInformationMessage(`coc-${name} installed`);
792
+ import_coc.window.showInformationMessage(`coc-${name} updated`);
796
793
  try {
797
794
  const meta = JSON.parse(fs3.readFileSync(metaPath(name), "utf-8"));
798
795
  if (meta.commit) {
@@ -814,7 +811,7 @@ async function updatePackage(state, name) {
814
811
  }
815
812
  async function runWithOutput(cmd, args, cwd) {
816
813
  return new Promise((resolve2, reject) => {
817
- const child = (0, import_child_process2.spawn)(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: true });
814
+ const child = (0, import_child_process2.spawn)(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
818
815
  const timer = setTimeout(() => {
819
816
  child.kill("SIGTERM");
820
817
  reject(new Error(`Timed out after ${CMD_TIMEOUT / 1e3}s: ${cmd} ${args.join(" ")}`));
@@ -1227,7 +1224,7 @@ var TUI = class {
1227
1224
  return;
1228
1225
  }
1229
1226
  if (id === "X" && entry.status === "installed") {
1230
- uninstallPackage(this.state, pkgName);
1227
+ await uninstallPackage(this.state, pkgName);
1231
1228
  return;
1232
1229
  }
1233
1230
  if (id === "R" && entry.status === "installed") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coc-vscode-loader",
3
- "version": "1.1.9",
3
+ "version": "1.2.1",
4
4
  "description": "Run VS Code extensions seamlessly in coc.nvim",
5
5
  "main": "lib/index.js",
6
6
  "keywords": [