Skip to content

Commit 980432c

Browse files
committed
Extract makeWindowsEditorProtocolTarget into shared editorProtocol module
Deduplicate makeWindowsEditorProtocolTarget, WINDOWS_EDITOR_URI_SCHEMES, splitLineColumnSuffix, and LINE_COLUMN_SUFFIX_PATTERN which were independently implemented in both open.ts and DesktopLauncher.ts. Both files now import from a single apps/server/src/editorProtocol.ts module, eliminating the risk of divergent behavior.
1 parent b909b94 commit 980432c

File tree

3 files changed

+45
-77
lines changed

3 files changed

+45
-77
lines changed

apps/server/src/editorProtocol.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { pathToFileURL } from "node:url";
2+
3+
import type { EditorId } from "@t3tools/contracts";
4+
5+
export const LINE_COLUMN_SUFFIX_PATTERN = /:\d+(?::\d+)?$/;
6+
7+
export const WINDOWS_EDITOR_URI_SCHEMES: Partial<Record<EditorId, string>> = {
8+
vscode: "vscode",
9+
"vscode-insiders": "vscode-insiders",
10+
vscodium: "vscodium",
11+
};
12+
13+
export function splitLineColumnSuffix(target: string): {
14+
readonly filePath: string;
15+
readonly suffix: string;
16+
} {
17+
const match = target.match(LINE_COLUMN_SUFFIX_PATTERN);
18+
if (!match) {
19+
return { filePath: target, suffix: "" };
20+
}
21+
22+
return {
23+
filePath: target.slice(0, -match[0].length),
24+
suffix: match[0],
25+
};
26+
}
27+
28+
export function makeWindowsEditorProtocolTarget(
29+
editor: EditorId,
30+
target: string,
31+
): string | undefined {
32+
const scheme = WINDOWS_EDITOR_URI_SCHEMES[editor];
33+
if (!scheme) return undefined;
34+
35+
const { filePath, suffix } = splitLineColumnSuffix(target);
36+
const fileUrl = pathToFileURL(filePath).href;
37+
const fileTarget = fileUrl.startsWith("file:///")
38+
? fileUrl.slice("file:///".length)
39+
: fileUrl.replace(/^file:\/\//, "");
40+
41+
return `${scheme}://file/${fileTarget}${suffix}`;
42+
}

apps/server/src/open.ts

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
import { spawn } from "node:child_process";
1010
import { accessSync, constants, statSync } from "node:fs";
1111
import { extname, join } from "node:path";
12-
import { pathToFileURL } from "node:url";
13-
1412
import { EDITORS, OpenError, type EditorId } from "@t3tools/contracts";
1513
import { Context, Effect, Layer } from "effect";
1614

15+
import { makeWindowsEditorProtocolTarget } from "./editorProtocol";
16+
1717
// ==============================
1818
// Definitions
1919
// ==============================
@@ -36,11 +36,6 @@ interface CommandAvailabilityOptions {
3636
}
3737

3838
const TARGET_WITH_POSITION_PATTERN = /^(.*?):(\d+)(?::(\d+))?$/;
39-
const WINDOWS_EDITOR_URI_SCHEMES: Partial<Record<EditorId, string>> = {
40-
vscode: "vscode",
41-
"vscode-insiders": "vscode-insiders",
42-
vscodium: "vscodium",
43-
};
4439

4540
function parseTargetPathAndPosition(target: string): {
4641
path: string;
@@ -59,36 +54,6 @@ function parseTargetPathAndPosition(target: string): {
5954
};
6055
}
6156

62-
function splitTargetPathAndSuffix(target: string): {
63-
path: string;
64-
suffix: string;
65-
} {
66-
const parsedTarget = parseTargetPathAndPosition(target);
67-
if (!parsedTarget) {
68-
return { path: target, suffix: "" };
69-
}
70-
71-
return {
72-
path: parsedTarget.path,
73-
suffix: parsedTarget.column
74-
? `:${parsedTarget.line}:${parsedTarget.column}`
75-
: `:${parsedTarget.line}`,
76-
};
77-
}
78-
79-
function makeWindowsEditorProtocolTarget(editor: EditorId, target: string): string | undefined {
80-
const scheme = WINDOWS_EDITOR_URI_SCHEMES[editor];
81-
if (!scheme) return undefined;
82-
83-
const { path, suffix } = splitTargetPathAndSuffix(target);
84-
const fileUrl = pathToFileURL(path).href;
85-
const fileTarget = fileUrl.startsWith("file:///")
86-
? fileUrl.slice("file:///".length)
87-
: fileUrl.replace(/^file:\/\//, "");
88-
89-
return `${scheme}://file/${fileTarget}${suffix}`;
90-
}
91-
9257
function resolveCommandEditorArgs(
9358
editor: (typeof EDITORS)[number],
9459
target: string,

apps/server/src/process/Layers/DesktopLauncher.ts

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
*/
1010
import OS from "node:os";
1111
import { spawn as spawnNodeChildProcess } from "node:child_process";
12-
import { pathToFileURL } from "node:url";
13-
1412
import { EDITORS, type EditorId } from "@t3tools/contracts";
1513
import { Array, Effect, FileSystem, Layer, Option, Path, Scope } from "effect";
1614
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
15+
import { LINE_COLUMN_SUFFIX_PATTERN, makeWindowsEditorProtocolTarget } from "../../editorProtocol";
1716
import {
1817
isWindowsBatchShim,
1918
makeWindowsCmdSpawnArguments,
@@ -113,20 +112,13 @@ type LaunchAttemptError =
113112

114113
type LaunchPlanError = LaunchAttemptError | DesktopLauncherLaunchAttemptsExhaustedError;
115114

116-
const LINE_COLUMN_SUFFIX_PATTERN = /:\d+(?::\d+)?$/;
117115
const WINDOWS_POWERSHELL_CANDIDATES = ["powershell.exe", "powershell", "pwsh.exe", "pwsh"] as const;
118116
const WSL_POWERSHELL_CANDIDATES = [
119117
"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe",
120118
"/mnt/c/Program Files/PowerShell/7/pwsh.exe",
121119
"powershell.exe",
122120
"pwsh.exe",
123121
] as const;
124-
const WINDOWS_EDITOR_URI_SCHEMES: Partial<Record<EditorId, string>> = {
125-
vscode: "vscode",
126-
"vscode-insiders": "vscode-insiders",
127-
vscodium: "vscodium",
128-
};
129-
130122
function shouldUseGotoFlag(editor: (typeof EDITORS)[number], target: string): boolean {
131123
return editor.supportsGoto && LINE_COLUMN_SUFFIX_PATTERN.test(target);
132124
}
@@ -135,24 +127,6 @@ function stripWrappingQuotes(value: string): string {
135127
return value.replace(/^"+|"+$/g, "");
136128
}
137129

138-
function splitLineColumnSuffix(target: string): {
139-
readonly filePath: string;
140-
readonly suffix: string;
141-
} {
142-
const match = target.match(LINE_COLUMN_SUFFIX_PATTERN);
143-
if (!match) {
144-
return {
145-
filePath: target,
146-
suffix: "",
147-
};
148-
}
149-
150-
return {
151-
filePath: target.slice(0, -match[0].length),
152-
suffix: match[0],
153-
};
154-
}
155-
156130
function resolvePathEnvironmentVariable(env: NodeJS.ProcessEnv): string {
157131
return env.PATH ?? env.Path ?? env.path ?? "";
158132
}
@@ -247,19 +221,6 @@ function shouldPreferWindowsOpenerOnWsl(input: OpenExternalInput, runtime: Launc
247221
return runtime.isWsl && !runtime.isInsideContainer && isUriLikeTarget(input.target);
248222
}
249223

250-
function makeWindowsEditorProtocolTarget(editor: EditorId, target: string): string | undefined {
251-
const scheme = WINDOWS_EDITOR_URI_SCHEMES[editor];
252-
if (!scheme) return undefined;
253-
254-
const { filePath, suffix } = splitLineColumnSuffix(target);
255-
const fileUrl = pathToFileURL(filePath).href;
256-
const fileTarget = fileUrl.startsWith("file:///")
257-
? fileUrl.slice("file:///".length)
258-
: fileUrl.replace(/^file:\/\//, "");
259-
260-
return `${scheme}://file/${fileTarget}${suffix}`;
261-
}
262-
263224
function makeLaunchPlan(
264225
command: string,
265226
args: ReadonlyArray<string>,

0 commit comments

Comments
 (0)