coc-vscode-loader 1.1.9 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.0",
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();
@@ -502,9 +508,23 @@ async function convertSource(inputDir, name, onProgress) {
502
508
  const log2 = (chunk) => onProgress(2, 5, chunk.trim(), "");
503
509
  await run("npm", ["install", "--legacy-peer-deps", "--production"], converterDir, log2);
504
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 presetsPath = path3.join(registryDir, "presets.json");
520
+ if (fs3.existsSync(presetsPath)) {
521
+ fs3.writeFileSync(presetsFile, fs3.readFileSync(presetsPath));
522
+ }
523
+ const args = ["tsx", cli, "convert", inputDir, "-o", build, "--convert-file", convertFile];
524
+ if (fs3.existsSync(presetsFile)) args.push("--presets-file", presetsFile);
505
525
  onProgress(2, 5, "Converting...", `converter convert ${inputDir} -o ${build}`);
506
526
  const log = (chunk) => onProgress(2, 5, chunk.trim(), "");
507
- await run("npx", ["tsx", cli, "convert", inputDir, "-o", build], cacheDir(name), log);
527
+ await run("npx", args, cacheDir(name), log);
508
528
  }
509
529
  async function buildPackage(name, inputDir, info, onProgress) {
510
530
  const build = buildDir(name);
@@ -614,56 +634,18 @@ async function buildPackage(name, inputDir, info, onProgress) {
614
634
  }
615
635
  const indexPath = path3.join(build, "lib", "index.js");
616
636
  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
637
  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
- );
638
+ code = code.replace(/\{\{version}}/g, version);
639
+ code = code.replace(/\{\{platform}}/g, platform);
640
+ code = code.replace(/\{\{arch}}/g, arch2);
641
+ code = code.replace(/\{\{raw-arch}}/g, rawArch);
642
+ code = code.replace(/\{\{rust-target}}/g, rustTarget);
633
643
  fs3.writeFileSync(indexPath, code);
634
644
  }
635
645
  } catch (e) {
636
646
  onProgress(4, 5, `Warning: serverBinary setup failed (${e.message})`, "install server binary manually");
637
647
  }
638
648
  }
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
649
  }
668
650
  function extensionsPkgPath() {
669
651
  return path3.join(os3.homedir(), ".config", "coc", "extensions", "package.json");
@@ -676,7 +658,7 @@ async function installToCoc(name, onProgress) {
676
658
  fs3.mkdirSync(path3.dirname(dest), { recursive: true });
677
659
  fs3.cpSync(src, dest, { recursive: true });
678
660
  const pkgPath = extensionsPkgPath();
679
- const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
661
+ const pkg = fs3.existsSync(pkgPath) ? JSON.parse(fs3.readFileSync(pkgPath, "utf-8")) : { dependencies: {} };
680
662
  pkg.dependencies = pkg.dependencies || {};
681
663
  const depName = `coc-${name}`;
682
664
  if (!pkg.dependencies[depName]) {
@@ -714,7 +696,7 @@ async function installPackage(state, name) {
714
696
  state.setPackageStatus(name, "installing", { progress: "Starting..." });
715
697
  try {
716
698
  const input = await downloadSource(info, name, prog);
717
- await convertSource(input, name, prog);
699
+ await convertSource(input, name, info, prog);
718
700
  await buildPackage(name, input, info, prog);
719
701
  await installToCoc(name, prog);
720
702
  await saveMeta(name);
@@ -786,13 +768,13 @@ async function updatePackage(state, name) {
786
768
  state.setPackageStatus(name, "updating", { progress: "Starting..." });
787
769
  try {
788
770
  const input = await downloadSource(info, name, prog);
789
- await convertSource(input, name, prog);
771
+ await convertSource(input, name, info, prog);
790
772
  await buildPackage(name, input, info, prog);
791
773
  await installToCoc(name, prog);
792
774
  await saveMeta(name);
793
775
  state.setDirty();
794
776
  state.setPackageStatus(name, "installed");
795
- import_coc.window.showInformationMessage(`coc-${name} installed`);
777
+ import_coc.window.showInformationMessage(`coc-${name} updated`);
796
778
  try {
797
779
  const meta = JSON.parse(fs3.readFileSync(metaPath(name), "utf-8"));
798
780
  if (meta.commit) {
@@ -814,7 +796,7 @@ async function updatePackage(state, name) {
814
796
  }
815
797
  async function runWithOutput(cmd, args, cwd) {
816
798
  return new Promise((resolve2, reject) => {
817
- const child = (0, import_child_process2.spawn)(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: true });
799
+ const child = (0, import_child_process2.spawn)(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
818
800
  const timer = setTimeout(() => {
819
801
  child.kill("SIGTERM");
820
802
  reject(new Error(`Timed out after ${CMD_TIMEOUT / 1e3}s: ${cmd} ${args.join(" ")}`));
@@ -1227,7 +1209,7 @@ var TUI = class {
1227
1209
  return;
1228
1210
  }
1229
1211
  if (id === "X" && entry.status === "installed") {
1230
- uninstallPackage(this.state, pkgName);
1212
+ await uninstallPackage(this.state, pkgName);
1231
1213
  return;
1232
1214
  }
1233
1215
  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.0",
4
4
  "description": "Run VS Code extensions seamlessly in coc.nvim",
5
5
  "main": "lib/index.js",
6
6
  "keywords": [