mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
Trim unused rules from the editor's same-file check (keep WSL folding for discard holds) (#31669)
* refactor(editor): drop SSH endpoint, path flavor and WSL alias folding from buffer identity SSH files are keyed by connection target; the guarded save catches a retarget. \\wsl$ and \\wsl.localhost stay distinct, including for discard holds and the per-file write lock: main compares WSL writes at write time. Co-Authored-By: Claude <noreply@anthropic.com> * fix(editor): keep WSL alias folding in discard-hold and write-lock keys A discard via \\wsl$ must hold autosave of a tab opened via \\wsl.localhost: the local guarded write is not atomic against git. Buffer identity stays unfolded; only the hold/lock key folds, on the local Windows client. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -56,4 +56,26 @@ describe('discard hold keys on a Windows client', () => {
|
||||
expect(key).not.toBe('a')
|
||||
expect(key).toBe(editorFileHoldKey(state, tab('b', 'c:/repo/a.ts', local)))
|
||||
})
|
||||
|
||||
it('holds a `\\\\wsl.localhost` tab while its file is discarded via `\\\\wsl$`', () => {
|
||||
const local = { worktreeId: 'wt-1' }
|
||||
const tabKey = editorFileHoldKey(
|
||||
state,
|
||||
tab('b', '\\\\wsl.localhost\\ubuntu\\repo\\a.ts', local)
|
||||
)
|
||||
const discarded = pathMutationHoldKeys(state, {
|
||||
worktreeId: 'wt-1',
|
||||
worktreePath: '\\\\wsl$\\Ubuntu\\repo',
|
||||
relativePath: 'a.ts'
|
||||
})
|
||||
expect(tabKey).not.toBe('b')
|
||||
expect(discarded.has(tabKey)).toBe(true)
|
||||
// The Linux path keeps its case, and a real share named `wsl` is another machine.
|
||||
expect(
|
||||
discarded.has(editorFileHoldKey(state, tab('c', '\\\\wsl$\\Ubuntu\\repo\\A.ts', local)))
|
||||
).toBe(false)
|
||||
expect(
|
||||
discarded.has(editorFileHoldKey(state, tab('d', '\\\\wsl\\Ubuntu\\repo\\a.ts', local)))
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,15 +4,26 @@ import { joinPath } from '@/lib/path'
|
||||
import { getOpenFilesForExternalFileChange, type EditorPathMutationTarget } from './editor-autosave'
|
||||
import { resolveEditorModelOwner, resolvedEditorModelOwnerKey } from './editor-model-owner'
|
||||
import type { EditorModelOwner } from './editor-model-owner'
|
||||
import { editorBufferOwnerPathKey } from '@/store/slices/editor/buffers/editor-buffer-identity'
|
||||
import {
|
||||
editorBufferOwnerPathKey,
|
||||
isLocalWindowsEditorOwner
|
||||
} from '@/store/slices/editor/buffers/editor-buffer-identity'
|
||||
import { parseWslUncPath } from '../../../../shared/wsl-paths'
|
||||
|
||||
// Why not the model URI: it normalizes paths by the client's rules, so on a Windows client
|
||||
// `/repo/a\b.ts` and `/repo/a/b.ts` on a POSIX host would collide.
|
||||
function physicalFileKey(owner: EditorModelOwner, filePath: string): string | null {
|
||||
const ownerKey = resolvedEditorModelOwnerKey(owner)
|
||||
return ownerKey === null
|
||||
? null
|
||||
: JSON.stringify([ownerKey, editorBufferOwnerPathKey(filePath, owner)])
|
||||
if (ownerKey === null) {
|
||||
return null
|
||||
}
|
||||
// Why: a discard via `\\wsl$` must hold a tab opened via `\\wsl.localhost`. A tuple can't
|
||||
// equal a real path string, so a share literally named `wsl` stays apart.
|
||||
const wsl = isLocalWindowsEditorOwner(owner) ? parseWslUncPath(filePath) : null
|
||||
const pathKey = wsl
|
||||
? ['wsl', wsl.distro.toLowerCase(), wsl.linuxPath]
|
||||
: editorBufferOwnerPathKey(filePath, owner)
|
||||
return JSON.stringify([ownerKey, pathKey])
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,9 +9,7 @@ import {
|
||||
detachedEditorBufferId,
|
||||
editorBufferIdentityOf,
|
||||
editorBufferPathKey,
|
||||
isEditorBufferIdentityRetired,
|
||||
type EditorBufferHostFacts,
|
||||
type EditorBufferHostPathFlavor
|
||||
isEditorBufferIdentityRetired
|
||||
} from './editor-buffer-identity'
|
||||
|
||||
const LOCAL: WorktreeOperationRoute = { executionHostId: 'local', runtimeEnvironmentId: null }
|
||||
@@ -45,18 +43,12 @@ function file(overrides: Partial<OpenFile> = {}, route?: WorktreeOperationRoute)
|
||||
}
|
||||
}
|
||||
|
||||
const HOST_A: EditorBufferHostFacts = { capturedSshEndpoint: 'host-a:22' }
|
||||
|
||||
function keyOf(tab: OpenFile, facts: EditorBufferHostFacts = HOST_A): string | undefined {
|
||||
return editorBufferIdentityOf(tab, {}, facts)?.key
|
||||
function keyOf(tab: OpenFile): string | undefined {
|
||||
return editorBufferIdentityOf(tab, {})?.key
|
||||
}
|
||||
|
||||
function pathKey(
|
||||
filePath: string,
|
||||
flavor: EditorBufferHostPathFlavor = 'unknown',
|
||||
foldWsl = false
|
||||
) {
|
||||
return JSON.stringify(editorBufferPathKey(filePath, flavor, foldWsl))
|
||||
function pathKey(filePath: string, windows = false) {
|
||||
return editorBufferPathKey(filePath, windows)
|
||||
}
|
||||
|
||||
function runtimeTab(id: string, pairingRevision?: number): OpenFile {
|
||||
@@ -96,50 +88,27 @@ describe('editorBufferPathKey', () => {
|
||||
expect(pathKey(left)).toBe(pathKey(right))
|
||||
})
|
||||
|
||||
it('folds the UNC server name of a forward-slash path on a Windows host only', () => {
|
||||
expect(pathKey('//SERVER/share/a.ts', 'windows')).toBe(
|
||||
pathKey('//server/share/a.ts', 'windows')
|
||||
)
|
||||
expect(pathKey('\\\\server\\share\\a.ts', 'windows')).toBe(
|
||||
pathKey('//SERVER/share/a.ts', 'windows')
|
||||
)
|
||||
it('folds the UNC server name of a forward-slash path on a Windows client only', () => {
|
||||
expect(pathKey('//SERVER/share/a.ts', true)).toBe(pathKey('//server/share/a.ts', true))
|
||||
expect(pathKey('\\\\server\\share\\a.ts', true)).toBe(pathKey('//SERVER/share/a.ts', true))
|
||||
})
|
||||
|
||||
it.each([
|
||||
['C:/Repo/A.ts', 'C:/repo/a.ts'],
|
||||
['\\\\server\\share\\A.ts', '\\\\server\\share\\a.ts'],
|
||||
['\\\\server\\Share\\a.ts', '\\\\server\\share\\a.ts'],
|
||||
['//wsl$/Ubuntu/home/me/A.ts', '//wsl$/Ubuntu/home/me/a.ts'],
|
||||
['\\\\wsl$\\Ubuntu\\home\\me\\a.ts', '\\\\wsl.localhost\\Ubuntu\\home\\me\\a.ts'],
|
||||
['/repo/A.ts', '/repo/a.ts'],
|
||||
['/repo/a\\b.ts', '/repo/a/b.ts']
|
||||
])('never folds path case or POSIX backslashes: %s is not %s', (left, right) => {
|
||||
expect(pathKey(left, 'windows', true)).not.toBe(pathKey(right, 'windows', true))
|
||||
])('never folds path case, WSL aliases or POSIX backslashes: %s is not %s', (left, right) => {
|
||||
expect(pathKey(left, true)).not.toBe(pathKey(right, true))
|
||||
})
|
||||
|
||||
it('keeps an ambiguous `//` path exact unless the host is known Windows', () => {
|
||||
it('keeps an ambiguous `//` path exact unless the client is Windows', () => {
|
||||
// Valid POSIX names on Linux or an SSH host: a backslash is part of the file name.
|
||||
expect(pathKey('//repo/a\\b.ts')).not.toBe(pathKey('//repo/a/b.ts'))
|
||||
expect(pathKey('//SERVER/share/a.ts')).not.toBe(pathKey('//server/share/a.ts'))
|
||||
expect(pathKey('//repo/a\\b.ts', 'windows')).toBe(pathKey('//repo/a/b.ts', 'windows'))
|
||||
})
|
||||
|
||||
it.each([
|
||||
['\\\\wsl$\\Ubuntu\\home\\me\\a.ts', '\\\\wsl.localhost\\ubuntu\\home\\me\\a.ts'],
|
||||
['//wsl$/Ubuntu/home/me/a.ts', '\\\\wsl.localhost\\Ubuntu\\home\\me\\a.ts']
|
||||
])('folds the WSL aliases %s and %s only when asked', (left, right) => {
|
||||
expect(pathKey(left, 'windows', true)).toBe(pathKey(right, 'windows', true))
|
||||
expect(pathKey(left, 'windows')).not.toBe(pathKey(right, 'windows'))
|
||||
})
|
||||
|
||||
it('never lets a folded WSL key equal a real share named wsl', () => {
|
||||
const folded = pathKey('\\\\wsl$\\Ubuntu\\home\\a.ts', 'windows', true)
|
||||
for (const real of [
|
||||
'\\\\wsl\\ubuntu\\home\\a.ts',
|
||||
'//wsl/ubuntu/home/a.ts',
|
||||
'\\\\wsl\\Ubuntu\\home\\a.ts'
|
||||
]) {
|
||||
expect(pathKey(real, 'windows', true)).not.toBe(folded)
|
||||
}
|
||||
expect(pathKey('//repo/a\\b.ts', true)).toBe(pathKey('//repo/a/b.ts', true))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -148,25 +117,19 @@ describe('editorBufferIdentityOf', () => {
|
||||
expect(keyOf(file({}, LOCAL))).toBe(keyOf(file({ id: 'b', worktreeId: 'wt-2' }, LOCAL)))
|
||||
})
|
||||
|
||||
it('folds WSL aliases for a local Windows client only', () => {
|
||||
it('keeps WSL aliases apart on a local Windows client', () => {
|
||||
vi.stubGlobal('navigator', { userAgent: 'Mozilla/5.0 (Windows NT 10.0)' })
|
||||
const wslDollar = file({ filePath: '\\\\wsl$\\Ubuntu\\home\\a.ts' }, LOCAL)
|
||||
const wslLocalhost = file({ id: 'b', filePath: '\\\\wsl.localhost\\Ubuntu\\home\\a.ts' }, LOCAL)
|
||||
vi.stubGlobal('navigator', { userAgent: 'Mozilla/5.0 (Macintosh)' })
|
||||
expect(keyOf(wslDollar)).not.toBe(keyOf(wslLocalhost))
|
||||
|
||||
vi.stubGlobal('navigator', { userAgent: 'Mozilla/5.0 (Windows NT 10.0)' })
|
||||
expect(keyOf(wslDollar)).toBe(keyOf(wslLocalhost))
|
||||
const ssh: WorktreeOperationRoute = {
|
||||
executionHostId: 'ssh:target',
|
||||
runtimeEnvironmentId: null
|
||||
}
|
||||
expect(keyOf(file({ filePath: wslDollar.filePath }, ssh))).not.toBe(
|
||||
keyOf(file({ filePath: wslLocalhost.filePath }, ssh))
|
||||
)
|
||||
})
|
||||
|
||||
it('keys an SSH target by its runtime environment as well as its target id', () => {
|
||||
it('keys an SSH file by its connection target and runtime environment', () => {
|
||||
const direct = keyOf(file({ externalSshTargetId: 'target', runtimeEnvironmentId: null }))
|
||||
const directElsewhere = keyOf(
|
||||
file({ id: 'b', worktreeId: 'wt-2', externalSshTargetId: 'target' })
|
||||
)
|
||||
const otherTarget = keyOf(file({ externalSshTargetId: 'target-2' }))
|
||||
const nested = keyOf(file({ externalSshTargetId: 'target', runtimeEnvironmentId: 'env' }))
|
||||
const nestedElsewhere = keyOf(
|
||||
file({
|
||||
@@ -181,41 +144,19 @@ describe('editorBufferIdentityOf', () => {
|
||||
)
|
||||
|
||||
expect(direct).toBeDefined()
|
||||
expect(new Set([direct, nested, otherRuntime]).size).toBe(3)
|
||||
expect(direct).toBe(directElsewhere)
|
||||
expect(new Set([direct, otherTarget, nested, otherRuntime]).size).toBe(4)
|
||||
expect(nested).toBe(nestedElsewhere)
|
||||
})
|
||||
|
||||
it('keys SSH tabs by the endpoint each captured, never the target id alone', () => {
|
||||
const tab = (id: string) =>
|
||||
file({ id, externalSshTargetId: 'target', runtimeEnvironmentId: null })
|
||||
// The target id now points at B; the old tab still carries the endpoint it opened on.
|
||||
const openedOnA = keyOf(tab('a'), { capturedSshEndpoint: 'host-a:22' })
|
||||
const openedOnB = keyOf(tab('b'), { capturedSshEndpoint: 'host-b:22' })
|
||||
expect(openedOnA).toBeDefined()
|
||||
expect(openedOnA).not.toBe(openedOnB)
|
||||
// A reconnect to the same machine captures the same endpoint.
|
||||
expect(keyOf(tab('c'), { capturedSshEndpoint: 'host-a:22' })).toBe(openedOnA)
|
||||
})
|
||||
|
||||
it('never shares an SSH tab with no captured endpoint', () => {
|
||||
const tab = file({ externalSshTargetId: 'target', runtimeEnvironmentId: null })
|
||||
expect(keyOf(tab, {})).toBeUndefined()
|
||||
expect(keyOf(tab, { capturedSshEndpoint: '' })).toBeUndefined()
|
||||
})
|
||||
|
||||
it('folds a forward-slash UNC server only on a remote host verified as Windows', () => {
|
||||
it('keeps a remote forward-slash UNC path apart from its backslash form', () => {
|
||||
const route: WorktreeOperationRoute = {
|
||||
executionHostId: 'runtime:env',
|
||||
runtimeEnvironmentId: 'env'
|
||||
}
|
||||
const upper = file({ filePath: '//SERVER/share/a.ts' }, route)
|
||||
const slash = file({ filePath: '//SERVER/share/a.ts' }, route)
|
||||
const backslash = file({ id: 'b', filePath: '\\\\SERVER\\share\\a.ts' }, route)
|
||||
expect(keyOf(upper, { remotePathFlavor: 'windows' })).toBe(
|
||||
keyOf(backslash, { remotePathFlavor: 'windows' })
|
||||
)
|
||||
for (const remotePathFlavor of ['posix', 'unknown'] as const) {
|
||||
expect(keyOf(upper, { remotePathFlavor })).not.toBe(keyOf(backslash, { remotePathFlavor }))
|
||||
}
|
||||
expect(keyOf(slash)).not.toBe(keyOf(backslash))
|
||||
})
|
||||
|
||||
it('keeps a runtime host and the local machine apart for one path', () => {
|
||||
@@ -223,8 +164,8 @@ describe('editorBufferIdentityOf', () => {
|
||||
})
|
||||
|
||||
it('never shares a tab whose owner is unresolved, or a non-edit tab', () => {
|
||||
expect(editorBufferIdentityOf(file({ runtimeEnvironmentId: null }), {}, HOST_A)).toBeNull()
|
||||
expect(editorBufferIdentityOf(file({ mode: 'diff' }, LOCAL), {}, HOST_A)).toBeNull()
|
||||
expect(editorBufferIdentityOf(file({ runtimeEnvironmentId: null }), {})).toBeNull()
|
||||
expect(editorBufferIdentityOf(file({ mode: 'diff' }, LOCAL), {})).toBeNull()
|
||||
})
|
||||
|
||||
it('joins a re-pair proven to be the same machine', () => {
|
||||
@@ -250,12 +191,12 @@ describe('editorBufferIdentityOf', () => {
|
||||
it('quarantines a retired host: its replacement with the same path stays apart', () => {
|
||||
replaceRuntimeEnvironmentRevisions([{ id: 'env', createdAt: 0, pairingRevision: 1 }])
|
||||
const old = runtimeTab('old')
|
||||
const local = editorBufferIdentityOf(file({}, LOCAL), {}, HOST_A)
|
||||
const local = editorBufferIdentityOf(file({}, LOCAL), {})
|
||||
const quarantine = new Map<string, string>()
|
||||
// Retirement fires before the new revision is published, while the old key still stands.
|
||||
const stop = onRuntimeEnvironmentsRetired((retired) => {
|
||||
for (const [viewId, identity] of [
|
||||
['old', editorBufferIdentityOf(old, {}, HOST_A)],
|
||||
['old', editorBufferIdentityOf(old, {})],
|
||||
['local', local]
|
||||
] as const) {
|
||||
if (identity && isEditorBufferIdentityRetired(identity, retired)) {
|
||||
|
||||
@@ -4,51 +4,24 @@ import { resolveEditorModelOwner } from '@/components/editor/editor-model-owner'
|
||||
import { isLocalWindowsDesktopClient } from '@/lib/desktop-window-chrome'
|
||||
import { resolveContinuedRuntimeEnvironmentRevision } from '@/runtime/runtime-environment-revision'
|
||||
import { parseExecutionHostId } from '../../../../../../shared/execution-host'
|
||||
import { parseWslUncPath } from '../../../../../../shared/wsl-paths'
|
||||
import { normalizeRuntimePathSeparators } from '../../../../../../shared/cross-platform-path'
|
||||
|
||||
/** The physical file an edit tab shows. Renderer-local; the workspace is never part of it. */
|
||||
export type EditorBufferIdentity = {
|
||||
/** Environment, host, pairing root, SSH endpoint and path key: equal keys are one file. */
|
||||
/** Environment, host, pairing root and path key: equal keys are one file. */
|
||||
key: string
|
||||
environmentId: string | null
|
||||
}
|
||||
|
||||
/** `windows` or `posix` only when verified for the host; `unknown` never folds an ambiguous path. */
|
||||
export type EditorBufferHostPathFlavor = 'windows' | 'posix' | 'unknown'
|
||||
|
||||
/** Facts about the tab's execution host, supplied per tab by the caller. */
|
||||
export type EditorBufferHostFacts = {
|
||||
/**
|
||||
* The SSH endpoint this tab captured when it opened. Never the target's current endpoint: a
|
||||
* target id can be edited in place to name another machine, and a live lookup would then match
|
||||
* an old tab to the new machine. Undefined for an SSH tab means it never shares.
|
||||
*/
|
||||
capturedSshEndpoint?: string
|
||||
/** Verified path flavor of a remote host. The local client's flavor is detected here. */
|
||||
remotePathFlavor?: EditorBufferHostPathFlavor
|
||||
}
|
||||
|
||||
/** A WSL key is a tuple, so no real path string can ever equal it. */
|
||||
export type EditorBufferPathKey = string | readonly ['wsl', string, string]
|
||||
|
||||
/**
|
||||
* Case is kept, because case sensitivity can differ per directory and a folded key would merge
|
||||
* distinct files. Why not `normalizeRuntimePathForComparison`: it lowercases whole Windows paths.
|
||||
* `windows` (the local Windows client only) also reads a `//` path as UNC.
|
||||
*/
|
||||
export function editorBufferPathKey(
|
||||
filePath: string,
|
||||
flavor: EditorBufferHostPathFlavor,
|
||||
foldWslAliases = false
|
||||
): EditorBufferPathKey {
|
||||
const wsl = foldWslAliases ? parseWslUncPath(filePath) : null
|
||||
if (wsl) {
|
||||
// Distro names are case-insensitive; the Linux path under them is not.
|
||||
return ['wsl', wsl.distro.toLowerCase(), wsl.linuxPath]
|
||||
}
|
||||
export function editorBufferPathKey(filePath: string, windows: boolean): string {
|
||||
// Absolute POSIX paths start with `/`, so a drive or `\\` prefix proves Windows. `//` does not.
|
||||
const drive = /^[A-Za-z]:[\\/]/.test(filePath)
|
||||
const unc = filePath.startsWith('\\\\') || (flavor === 'windows' && filePath.startsWith('//'))
|
||||
const unc = filePath.startsWith('\\\\') || (windows && filePath.startsWith('//'))
|
||||
if (!drive && !unc) {
|
||||
return filePath
|
||||
}
|
||||
@@ -63,24 +36,25 @@ export function editorBufferPathKey(
|
||||
: `${slashed.slice(0, serverEnd).toLowerCase()}${slashed.slice(serverEnd)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* The path key of a file on a resolved owner, using only what the renderer verifies itself: a
|
||||
* remote host's flavor is unknown here, so only paths that prove Windows (drive, `\\`) fold.
|
||||
*/
|
||||
export function editorBufferOwnerPathKey(
|
||||
filePath: string,
|
||||
owner: { environmentId: string | null; hostId: string | null }
|
||||
): EditorBufferPathKey {
|
||||
const localWindows =
|
||||
type EditorPathOwner = { environmentId: string | null; hostId: string | null }
|
||||
|
||||
/** The owner is this machine and this client is the local Windows desktop app. */
|
||||
export function isLocalWindowsEditorOwner(owner: EditorPathOwner): boolean {
|
||||
return (
|
||||
parseExecutionHostId(owner.hostId)?.kind === 'local' &&
|
||||
!owner.environmentId &&
|
||||
isLocalWindowsDesktopClient()
|
||||
return editorBufferPathKey(filePath, localWindows ? 'windows' : 'unknown', localWindows)
|
||||
)
|
||||
}
|
||||
|
||||
/** The path key of a file on a resolved owner. Remote paths fold only when they prove Windows. */
|
||||
export function editorBufferOwnerPathKey(filePath: string, owner: EditorPathOwner): string {
|
||||
return editorBufferPathKey(filePath, isLocalWindowsEditorOwner(owner))
|
||||
}
|
||||
|
||||
/**
|
||||
* Null for tabs that never share: non-edit views, unresolved owners, and SSH tabs with no captured
|
||||
* endpoint.
|
||||
* Null for non-edit views and unresolved owners. SSH files are keyed by connection target; a
|
||||
* retarget is caught by the guarded save.
|
||||
*
|
||||
* The key is not fixed for a live tab: an unpinned runtime tab resolves its pairing live (null
|
||||
* before the revision registry loads, the newest proven revision after). A caller must treat a key
|
||||
@@ -88,21 +62,17 @@ export function editorBufferOwnerPathKey(
|
||||
*/
|
||||
export function editorBufferIdentityOf(
|
||||
file: OpenFile,
|
||||
state: WorktreeOperationRouteState,
|
||||
hostFacts: EditorBufferHostFacts
|
||||
state: WorktreeOperationRouteState
|
||||
): EditorBufferIdentity | null {
|
||||
if (file.mode !== 'edit') {
|
||||
return null
|
||||
}
|
||||
const { environmentId, hostId } = resolveEditorModelOwner(file, state)
|
||||
const host = parseExecutionHostId(hostId)
|
||||
const owner = resolveEditorModelOwner(file, state)
|
||||
const host = parseExecutionHostId(owner.hostId)
|
||||
if (!host) {
|
||||
return null
|
||||
}
|
||||
const sshEndpoint = host.kind === 'ssh' ? (hostFacts.capturedSshEndpoint ?? null) : null
|
||||
if (host.kind === 'ssh' && !sshEndpoint) {
|
||||
return null
|
||||
}
|
||||
const { environmentId } = owner
|
||||
// A same-host re-pair resolves to the revision it continues; an unproven one stays apart.
|
||||
const revisionRoot = environmentId
|
||||
? (resolveContinuedRuntimeEnvironmentRevision(
|
||||
@@ -110,21 +80,12 @@ export function editorBufferIdentityOf(
|
||||
file.operationProvenance?.generation.runtimePairingRevision
|
||||
) ?? null)
|
||||
: null
|
||||
// `wsl$` and `wsl.localhost` name one distro only on the local Windows client; a paired web
|
||||
// client (including `orca serve` on Windows) keeps them apart, as today.
|
||||
const localWindows = host.kind === 'local' && !environmentId && isLocalWindowsDesktopClient()
|
||||
const flavor = localWindows
|
||||
? 'windows'
|
||||
: host.kind === 'local' && !environmentId
|
||||
? 'unknown'
|
||||
: (hostFacts.remotePathFlavor ?? 'unknown')
|
||||
return {
|
||||
key: JSON.stringify([
|
||||
environmentId,
|
||||
host.id,
|
||||
revisionRoot,
|
||||
sshEndpoint,
|
||||
editorBufferPathKey(file.filePath, flavor, localWindows)
|
||||
editorBufferOwnerPathKey(file.filePath, owner)
|
||||
]),
|
||||
environmentId
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user