diff --git a/CHANGELOG.md b/CHANGELOG.md index bcf35b34edaff..951fa6e99da66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Adds dynamic model loading for GitHub Models and HuggingFace models - Adds a `gitlens.ai.modelOptions.temperature` setting to specify the temperature (randomness) for AI models that support it - Adds a _Switch Model_ button to the AI confirmation prompts +- Adds Windsurf support — closes [#3969](https://github.com/gitkraken/vscode-gitlens/issues/3969) ### Changed @@ -22,6 +23,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ### Fixed +- Fixes [#3952](https://github.com/gitkraken/vscode-gitlens/issues/3952) - Interactive rebase doesn't work in GL without VS Code added to path - Fixes [#3938](https://github.com/gitkraken/vscode-gitlens/issues/3938) - GitLens automatically initiating an external sign-in after install on vscode.dev - Fixes [#3946](https://github.com/gitkraken/vscode-gitlens/issues/3946) - Home View doesn't update repo state changes made when hidden diff --git a/src/commands/git/rebase.ts b/src/commands/git/rebase.ts index 4ba955b74d991..e3894b6a54ad3 100644 --- a/src/commands/git/rebase.ts +++ b/src/commands/git/rebase.ts @@ -12,7 +12,7 @@ import type { DirectiveQuickPickItem } from '../../quickpicks/items/directive'; import { createDirectiveQuickPickItem, Directive } from '../../quickpicks/items/directive'; import type { FlagsQuickPickItem } from '../../quickpicks/items/flags'; import { createFlagsQuickPickItem } from '../../quickpicks/items/flags'; -import { getEditorCommand } from '../../system/-webview/vscode'; +import { getHostEditorCommand } from '../../system/-webview/vscode'; import { pluralize } from '../../system/string'; import type { ViewsWithRepositoryFolders } from '../../views/viewBase'; import type { @@ -87,7 +87,7 @@ export class RebaseGitCommand extends QuickCommand { if (state.flags.includes('--interactive')) { await this.container.rebaseEditor.enableForNextUse(); - const editor = getEditorCommand(); + const editor = await getHostEditorCommand(); configs = ['-c', `"sequence.editor=${editor}"`]; } diff --git a/src/env/browser/platform.ts b/src/env/browser/platform.ts index 59f3aabc6a862..30ee4903fb7e7 100644 --- a/src/env/browser/platform.ts +++ b/src/env/browser/platform.ts @@ -1,3 +1,5 @@ +import type { Platform } from '../node/platform'; + export const isWeb = true; const _platform = (navigator as any)?.userAgentData?.platform; @@ -7,7 +9,7 @@ export const isLinux = _platform === 'Linux' || _userAgent.includes('Linux'); export const isMac = _platform === 'macOS' || _userAgent.includes('Macintosh'); export const isWindows = _platform === 'Windows' || _userAgent.includes('Windows'); -export function getPlatform(): string { +export function getPlatform(): Platform { if (isWindows) return 'web-windows'; if (isMac) return 'web-macOS'; if (isLinux) return 'web-linux'; diff --git a/src/env/node/git/git.ts b/src/env/node/git/git.ts index 89f554d681456..ced981673c79d 100644 --- a/src/env/node/git/git.ts +++ b/src/env/node/git/git.ts @@ -38,7 +38,7 @@ import { parseGitRemoteUrl } from '../../../git/parsers/remoteParser'; import { isUncommitted, isUncommittedStaged, shortenRevision } from '../../../git/utils/revision.utils'; import { configuration } from '../../../system/-webview/configuration'; import { splitPath } from '../../../system/-webview/path'; -import { getEditorCommand } from '../../../system/-webview/vscode'; +import { getHostEditorCommand } from '../../../system/-webview/vscode'; import { splitAt } from '../../../system/array'; import { log } from '../../../system/decorators/log'; import { join } from '../../../system/iterable'; @@ -2322,7 +2322,7 @@ export class Git { const git = normalizePath(location.path ?? 'git'); const coreEditorConfig = configuration.get('terminal.overrideGitEditor') - ? `-c "core.editor=${getEditorCommand()}" ` + ? `-c "core.editor=${await getHostEditorCommand()}" ` : ''; const parsedArgs = args.map(arg => (arg.startsWith('#') || /['();$|>&<]/.test(arg) ? `"${arg}"` : arg)); diff --git a/src/env/node/platform.ts b/src/env/node/platform.ts index daec4e98ee097..9535c1c78e871 100644 --- a/src/env/node/platform.ts +++ b/src/env/node/platform.ts @@ -9,7 +9,10 @@ export const isLinux = platform === 'linux'; export const isMac = platform === 'darwin'; export const isWindows = platform === 'win32'; -export function getPlatform(): string { +type OperatingSystems = 'windows' | 'macOS' | 'linux' | 'unknown'; +export type Platform = OperatingSystems | 'web' | `web-${OperatingSystems}` | 'unknown'; + +export function getPlatform(): Platform { if (isWindows) return 'windows'; if (isMac) return 'macOS'; if (isLinux) return 'linux'; diff --git a/src/system/-webview/vscode.ts b/src/system/-webview/vscode.ts index 3dfc26e13f7e0..e4bb84c0c1450 100644 --- a/src/system/-webview/vscode.ts +++ b/src/system/-webview/vscode.ts @@ -8,12 +8,13 @@ import type { WorkspaceFolder, } from 'vscode'; import { version as codeVersion, ColorThemeKind, env, Uri, ViewColumn, window, workspace } from 'vscode'; +import { getPlatform } from '@env/platform'; import type { IconPath } from '../../@types/vscode.iconpath'; import { imageMimetypes, Schemes, trackableSchemes } from '../../constants'; import type { Container } from '../../container'; import { isGitUri } from '../../git/gitUri'; import { Logger } from '../logger'; -import { extname, normalizePath } from '../path'; +import { extname, joinPaths, normalizePath } from '../path'; import { satisfies } from '../version'; import { executeCoreCommand } from './command'; import { configuration } from './configuration'; @@ -70,26 +71,81 @@ export function findOrOpenEditors(uris: Uri[], options?: TextDocumentShowOptions } } -export function getEditorCommand(): string { - let editor; +let _hostExecutablePath: string | undefined; +export async function getHostExecutablePath(): Promise { + if (_hostExecutablePath != null) return _hostExecutablePath; + + const platform = getPlatform(); + + let app: string; switch (env.appName) { + case 'Visual Studio Code': + app = 'code'; + break; case 'Visual Studio Code - Insiders': - editor = 'code-insiders --wait --reuse-window'; + app = 'code-insiders'; break; case 'Visual Studio Code - Exploration': - editor = 'code-exploration --wait --reuse-window'; + app = 'code-exploration'; break; case 'VSCodium': - editor = 'codium --wait --reuse-window'; + app = 'codium'; break; case 'Cursor': - editor = 'cursor --wait --reuse-window'; + app = 'cursor'; break; - default: - editor = 'code --wait --reuse-window'; + case 'Windsurf': + app = 'windsurf'; + break; + default: { + try { + const bytes = await workspace.fs.readFile(Uri.file(joinPaths(env.appRoot, 'product.json'))); + const product = JSON.parse(new TextDecoder().decode(bytes)); + app = product.applicationName; + } catch { + app = 'code'; + } + + break; + } + } + + _hostExecutablePath = app; + if (env.remoteName) return app; + + async function checkPath(path: string) { + try { + await workspace.fs.stat(Uri.file(path)); + return path; + } catch { + return undefined; + } + } + + switch (platform) { + case 'windows': + case 'linux': + _hostExecutablePath = + (await checkPath(joinPaths(env.appRoot, '..', '..', 'bin', app))) ?? + (await checkPath(joinPaths(env.appRoot, 'bin', app))) ?? + app; break; + case 'macOS': + _hostExecutablePath = + (await checkPath(joinPaths(env.appRoot, 'bin', app))) ?? + (await checkPath(joinPaths(env.appRoot, '..', '..', 'bin', app))) ?? + app; + break; + default: + throw new Error(`Unsupported platform: ${platform}`); } - return editor; + + return _hostExecutablePath; +} + +export async function getHostEditorCommand(): Promise { + const path = normalizePath(await getHostExecutablePath()).replace(/ /g, '\\ '); + return `${path} --wait --reuse-window`; } export function getEditorIfActive(document: TextDocument): TextEditor | undefined {