|
1 | 1 | import { execFileSync } from 'node:child_process';
|
2 |
| -import { join, delimiter } from 'node:path'; |
| 2 | +import { join, delimiter, normalize } from 'node:path'; |
3 | 3 | import { constants, existsSync, accessSync } from 'node:fs';
|
4 | 4 |
|
5 | 5 | export type NvimVersion = {
|
@@ -31,6 +31,10 @@ export type FindNvimOptions = {
|
31 | 31 | * - Example: `['0.4.4', '0.5.0', '0.4.3']`
|
32 | 32 | */
|
33 | 33 | readonly orderBy?: 'desc' | 'none';
|
| 34 | + /** |
| 35 | + * (Optional) Stop searching after found a valid match |
| 36 | + */ |
| 37 | + readonly firstMatch?: boolean; |
34 | 38 | };
|
35 | 39 |
|
36 | 40 | export type FindNvimResult = {
|
@@ -113,19 +117,67 @@ function compareVersions(a: string, b: string): number {
|
113 | 117 | return 0;
|
114 | 118 | }
|
115 | 119 |
|
| 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 | + |
116 | 167 | /**
|
117 | 168 | * Tries to find a usable `nvim` binary on the current system.
|
118 | 169 | *
|
119 | 170 | * @param opt.minVersion See {@link FindNvimOptions.minVersion}
|
120 | 171 | * @param opt.orderBy See {@link FindNvimOptions.orderBy}
|
| 172 | + * @param opt.firstMatch See {@link FindNvimOptions.firstMatch} |
121 | 173 | */
|
122 | 174 | 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 | + |
125 | 177 | const matches = new Array<NvimVersion>();
|
126 | 178 | 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'); |
129 | 181 | if (existsSync(nvimPath)) {
|
130 | 182 | try {
|
131 | 183 | accessSync(nvimPath, constants.X_OK);
|
@@ -154,6 +206,13 @@ export function findNvim(opt: FindNvimOptions = {}): Readonly<FindNvimResult> {
|
154 | 206 | buildType: buildTypeMatch[1],
|
155 | 207 | luaJitVersion: luaJitVersionMatch[1],
|
156 | 208 | });
|
| 209 | + |
| 210 | + if (opt.firstMatch) { |
| 211 | + return { |
| 212 | + matches, |
| 213 | + invalid, |
| 214 | + } as const; |
| 215 | + } |
157 | 216 | }
|
158 | 217 | }
|
159 | 218 | } catch (e) {
|
|
0 commit comments