Skip to content

Commit 78100f6

Browse files
authored
feat(findNvim): add "firstMatch" param, search common locations #397
Close #370 Close #267
1 parent 538c1c4 commit 78100f6

File tree

2 files changed

+80
-5
lines changed

2 files changed

+80
-5
lines changed

packages/neovim/src/utils/findNvim.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,20 @@ describe('findNvim', () => {
103103
error: undefined,
104104
});
105105
});
106+
107+
it('stops searching on first match when firstMatch is True', () => {
108+
const nvimRes = findNvim({ minVersion: '0.3.0', firstMatch: true });
109+
expect(nvimRes).toEqual({
110+
matches: expect.any(Array),
111+
invalid: expect.any(Array),
112+
});
113+
expect(nvimRes.matches.length).toEqual(1);
114+
expect(nvimRes.matches[0]).toEqual({
115+
nvimVersion: expect.any(String),
116+
path: expect.any(String),
117+
buildType: expect.any(String),
118+
luaJitVersion: expect.any(String),
119+
error: undefined,
120+
});
121+
});
106122
});

packages/neovim/src/utils/findNvim.ts

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { execFileSync } from 'node:child_process';
2-
import { join, delimiter } from 'node:path';
2+
import { join, delimiter, normalize } from 'node:path';
33
import { constants, existsSync, accessSync } from 'node:fs';
44

55
export type NvimVersion = {
@@ -31,6 +31,10 @@ export type FindNvimOptions = {
3131
* - Example: `['0.4.4', '0.5.0', '0.4.3']`
3232
*/
3333
readonly orderBy?: 'desc' | 'none';
34+
/**
35+
* (Optional) Stop searching after found a valid match
36+
*/
37+
readonly firstMatch?: boolean;
3438
};
3539

3640
export type FindNvimResult = {
@@ -113,19 +117,67 @@ function compareVersions(a: string, b: string): number {
113117
return 0;
114118
}
115119

120+
function getPlatformPaths() {
121+
const paths = new Set<string>();
122+
const { PATH, USERPROFILE, LOCALAPPDATA, PROGRAMFILES, HOME } = process.env;
123+
124+
const normalizePath = (path: string) =>
125+
normalize(windows ? path.toLowerCase() : path);
126+
127+
PATH?.split(delimiter).forEach(p => paths.add(normalizePath(p)));
128+
129+
// Add common Neovim installation paths not always in the system's PATH.
130+
if (windows) {
131+
// Scoop common install location
132+
if (USERPROFILE) {
133+
paths.add(normalizePath(`${USERPROFILE}/scoop/shims`));
134+
}
135+
paths.add(normalizePath('C:/ProgramData/scoop/shims'));
136+
137+
// Winget common install location
138+
// See https://github.com/microsoft/winget-cli/blob/master/doc/specs/%23182%20-%20Support%20for%20installation%20of%20portable%20standalone%20apps.md
139+
if (LOCALAPPDATA) {
140+
paths.add(normalizePath(`${LOCALAPPDATA}/Microsoft/WindowsApps`));
141+
paths.add(normalizePath(`${LOCALAPPDATA}/Microsoft/WinGet/Packages`));
142+
}
143+
if (PROGRAMFILES) {
144+
paths.add(normalizePath(`${PROGRAMFILES}/Neovim/bin`));
145+
paths.add(normalizePath(`${PROGRAMFILES} (x86)/Neovim/bin`));
146+
paths.add(normalizePath(`${PROGRAMFILES}/WinGet/Packages`));
147+
paths.add(normalizePath(`${PROGRAMFILES} (x86)/WinGet/Packages`));
148+
}
149+
} else {
150+
[
151+
'/usr/local/bin',
152+
'/usr/bin',
153+
'/opt/homebrew/bin',
154+
'/home/linuxbrew/.linuxbrew/bin',
155+
'/snap/nvim/current/usr/bin',
156+
].forEach(p => paths.add(p));
157+
158+
if (HOME) {
159+
paths.add(normalizePath(`${HOME}/bin`));
160+
paths.add(normalizePath(`${HOME}/.linuxbrew/bin`));
161+
}
162+
}
163+
164+
return paths;
165+
}
166+
116167
/**
117168
* Tries to find a usable `nvim` binary on the current system.
118169
*
119170
* @param opt.minVersion See {@link FindNvimOptions.minVersion}
120171
* @param opt.orderBy See {@link FindNvimOptions.orderBy}
172+
* @param opt.firstMatch See {@link FindNvimOptions.firstMatch}
121173
*/
122174
export function findNvim(opt: FindNvimOptions = {}): Readonly<FindNvimResult> {
123-
const paths = process.env.PATH?.split(delimiter) ?? [];
124-
const pathLength = paths.length;
175+
const paths = getPlatformPaths();
176+
125177
const matches = new Array<NvimVersion>();
126178
const invalid = new Array<NvimVersion>();
127-
for (let i = 0; i !== pathLength; i = i + 1) {
128-
const nvimPath = join(paths[i], windows ? 'nvim.exe' : 'nvim');
179+
for (const path of paths) {
180+
const nvimPath = join(path, windows ? 'nvim.exe' : 'nvim');
129181
if (existsSync(nvimPath)) {
130182
try {
131183
accessSync(nvimPath, constants.X_OK);
@@ -154,6 +206,13 @@ export function findNvim(opt: FindNvimOptions = {}): Readonly<FindNvimResult> {
154206
buildType: buildTypeMatch[1],
155207
luaJitVersion: luaJitVersionMatch[1],
156208
});
209+
210+
if (opt.firstMatch) {
211+
return {
212+
matches,
213+
invalid,
214+
} as const;
215+
}
157216
}
158217
}
159218
} catch (e) {

0 commit comments

Comments
 (0)