@mrclrchtr/supi-lsp 0.1.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.
package/utils.ts ADDED
@@ -0,0 +1,139 @@
1
+ // URI and language utilities for LSP.
2
+
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+
6
+ // ── URI Handling ──────────────────────────────────────────────────────
7
+
8
+ /** Convert a file path to a file:// URI. */
9
+ export function fileToUri(filePath: string): string {
10
+ const resolved = path.resolve(filePath);
11
+ if (process.platform === "win32") {
12
+ return `file:///${resolved.replace(/\\/g, "/")}`;
13
+ }
14
+ return `file://${resolved}`;
15
+ }
16
+
17
+ /** Convert a file:// URI to a file path. */
18
+ export function uriToFile(uri: string): string {
19
+ if (!uri.startsWith("file://")) return uri;
20
+ let filePath = decodeURIComponent(uri.slice(7));
21
+ if (
22
+ process.platform === "win32" &&
23
+ filePath.startsWith("/") &&
24
+ /^[A-Za-z]:/.test(filePath.slice(1))
25
+ ) {
26
+ filePath = filePath.slice(1);
27
+ }
28
+ return filePath;
29
+ }
30
+
31
+ // ── Language ID Detection ─────────────────────────────────────────────
32
+
33
+ const EXT_TO_LANGUAGE: Record<string, string> = {
34
+ ts: "typescript",
35
+ tsx: "typescriptreact",
36
+ js: "javascript",
37
+ jsx: "javascriptreact",
38
+ mts: "typescript",
39
+ cts: "typescript",
40
+ mjs: "javascript",
41
+ cjs: "javascript",
42
+ py: "python",
43
+ pyi: "python",
44
+ rs: "rust",
45
+ go: "go",
46
+ mod: "go",
47
+ c: "c",
48
+ h: "c",
49
+ cpp: "cpp",
50
+ hpp: "cpp",
51
+ cc: "cpp",
52
+ cxx: "cpp",
53
+ hxx: "cpp",
54
+ "c++": "cpp",
55
+ "h++": "cpp",
56
+ json: "json",
57
+ yaml: "yaml",
58
+ yml: "yaml",
59
+ md: "markdown",
60
+ html: "html",
61
+ css: "css",
62
+ scss: "scss",
63
+ sh: "shellscript",
64
+ bash: "shellscript",
65
+ toml: "toml",
66
+ xml: "xml",
67
+ sql: "sql",
68
+ rb: "ruby",
69
+ java: "java",
70
+ kt: "kotlin",
71
+ swift: "swift",
72
+ lua: "lua",
73
+ zig: "zig",
74
+ };
75
+
76
+ /** Detect the LSP languageId from a file path. */
77
+ export function detectLanguageId(filePath: string): string {
78
+ const ext = path.extname(filePath).slice(1).toLowerCase();
79
+ return EXT_TO_LANGUAGE[ext] ?? ext;
80
+ }
81
+
82
+ /** Get the file extension (without dot) from a file path. */
83
+ export function getFileExtension(filePath: string): string {
84
+ return path.extname(filePath).slice(1).toLowerCase();
85
+ }
86
+
87
+ // ── Root Marker Detection ─────────────────────────────────────────────
88
+
89
+ /**
90
+ * Search upward from `startDir` for any of the `markers` files/dirs.
91
+ * Returns the directory containing the first found marker, or `fallback`.
92
+ */
93
+ export function findProjectRoot(startDir: string, markers: string[], fallback: string): string {
94
+ let dir = path.resolve(startDir);
95
+ const root = path.parse(dir).root;
96
+
97
+ while (dir !== root) {
98
+ for (const marker of markers) {
99
+ if (fs.existsSync(path.join(dir, marker))) {
100
+ return dir;
101
+ }
102
+ }
103
+ const parent = path.dirname(dir);
104
+ if (parent === dir) break;
105
+ dir = parent;
106
+ }
107
+
108
+ return fallback;
109
+ }
110
+
111
+ // ── PATH Validation ───────────────────────────────────────────────────
112
+
113
+ /**
114
+ * Check if a command exists on PATH.
115
+ * Uses synchronous check to avoid complexity.
116
+ */
117
+ export function commandExists(command: string): boolean {
118
+ // If it's an absolute path, check directly
119
+ if (path.isAbsolute(command)) {
120
+ return fs.existsSync(command);
121
+ }
122
+
123
+ const pathDirs = (process.env.PATH ?? "").split(path.delimiter);
124
+ const extensions =
125
+ process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
126
+
127
+ for (const dir of pathDirs) {
128
+ for (const ext of extensions) {
129
+ const fullPath = path.join(dir, command + ext);
130
+ try {
131
+ fs.accessSync(fullPath, fs.constants.X_OK);
132
+ return true;
133
+ } catch {
134
+ // Not found here, continue
135
+ }
136
+ }
137
+ }
138
+ return false;
139
+ }