fix(terminal): reveal Markdown links at target lines (#9518)
* fix(terminal): reveal Markdown links at target lines * fix(terminal): scope line reveals to opened tabs --------- Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
This commit is contained in:
parent
d1ccfcff40
commit
d50ea090cf
|
|
@ -183,12 +183,13 @@ export function openDetectedFilePath(
|
|||
activateAndRevealWorktree(worktreeId)
|
||||
}
|
||||
|
||||
const language = detectLanguage(mappedFilePath)
|
||||
store.openFile(
|
||||
{
|
||||
filePath: mappedFilePath,
|
||||
relativePath,
|
||||
worktreeId: worktreeId || '',
|
||||
language: detectLanguage(mappedFilePath),
|
||||
language,
|
||||
mode: 'edit',
|
||||
runtimeEnvironmentId
|
||||
},
|
||||
|
|
@ -196,6 +197,14 @@ export function openDetectedFilePath(
|
|||
)
|
||||
|
||||
if (line !== null) {
|
||||
const openedStore = useAppStore.getState()
|
||||
// Why: scope the reveal to the opened editor tab id so owner-qualified tabs
|
||||
// across local/SSH/runtime contexts get it instead of an ambiguous path key.
|
||||
const fileId = openedStore.activeFileIdByWorktree[worktreeId] ?? mappedFilePath
|
||||
if (language === 'markdown') {
|
||||
// Why: rich Markdown has no line-based reveal consumer; line links must mount Monaco.
|
||||
openedStore.setMarkdownViewMode(fileId, 'source')
|
||||
}
|
||||
const targetColumn = column ?? 1
|
||||
store.setPendingEditorReveal(null)
|
||||
schedulePendingEditorReveal(() => {
|
||||
|
|
@ -204,6 +213,7 @@ export function openDetectedFilePath(
|
|||
}
|
||||
store.setPendingEditorReveal({
|
||||
filePath: mappedFilePath,
|
||||
fileId,
|
||||
line,
|
||||
column: targetColumn,
|
||||
matchLength: 0
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ const runtimeEnvironmentTransportCallMock = vi.fn()
|
|||
const setActiveWorktreeMock = vi.fn()
|
||||
const createBrowserTabMock = vi.fn()
|
||||
const setPendingEditorRevealMock = vi.fn()
|
||||
const setMarkdownViewModeMock = vi.fn()
|
||||
|
||||
const deps = { worktreeId: 'wt-1', worktreePath: '/tmp' }
|
||||
const storeState = {
|
||||
|
|
@ -52,6 +53,8 @@ const storeState = {
|
|||
createBrowserTab: createBrowserTabMock,
|
||||
openFile: openFileMock,
|
||||
setPendingEditorReveal: setPendingEditorRevealMock,
|
||||
setMarkdownViewMode: setMarkdownViewModeMock,
|
||||
activeFileIdByWorktree: {} as Record<string, string | null>,
|
||||
worktreesByRepo: {} as Record<string, { id: string; path: string }[]>
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +65,7 @@ vi.mock('@/store', () => ({
|
|||
}))
|
||||
|
||||
vi.mock('@/lib/language-detect', () => ({
|
||||
detectLanguage: () => 'plaintext'
|
||||
detectLanguage: (filePath: string) => (filePath.endsWith('.md') ? 'markdown' : 'plaintext')
|
||||
}))
|
||||
|
||||
// Why: the real helper reads worktreesByRepo/activeRepoId/etc. from the store
|
||||
|
|
@ -108,6 +111,7 @@ beforeEach(() => {
|
|||
vi.mocked(getConnectionId).mockReturnValue(null)
|
||||
openFilePathMock.mockResolvedValue(true)
|
||||
storeState.settings = undefined
|
||||
storeState.activeFileIdByWorktree = {}
|
||||
storeState.worktreesByRepo = {}
|
||||
registerHttpLinkStoreAccessor(() => storeState)
|
||||
vi.stubGlobal('window', {
|
||||
|
|
@ -371,6 +375,7 @@ describe('handleOscLink', () => {
|
|||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(1, null)
|
||||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(2, {
|
||||
filePath: '/tmp/src/main.ts',
|
||||
fileId: '/tmp/src/main.ts',
|
||||
line: 42,
|
||||
column: 1,
|
||||
matchLength: 0
|
||||
|
|
@ -388,6 +393,7 @@ describe('handleOscLink', () => {
|
|||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(1, null)
|
||||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(2, {
|
||||
filePath: '/tmp/src/main.ts',
|
||||
fileId: '/tmp/src/main.ts',
|
||||
line: 42,
|
||||
column: 7,
|
||||
matchLength: 0
|
||||
|
|
@ -395,6 +401,50 @@ describe('handleOscLink', () => {
|
|||
expect(openFilePathMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('opens terminal markdown line links in source mode so Monaco can reveal the line', async () => {
|
||||
setPlatform('Macintosh')
|
||||
const filePath = '/tmp/docs/terminal-scroll-intent-architecture.md'
|
||||
const fileId = 'editor:wt-1:runtime-1:terminal-scroll-intent-architecture.md'
|
||||
openFileMock.mockImplementationOnce(() => {
|
||||
storeState.activeFileIdByWorktree['wt-1'] = fileId
|
||||
})
|
||||
|
||||
openDetectedFilePath(filePath, 230, null, deps)
|
||||
await flushAsyncWork()
|
||||
await flushDoubleRaf()
|
||||
|
||||
expect(setMarkdownViewModeMock).toHaveBeenCalledWith(fileId, 'source')
|
||||
expect(setPendingEditorRevealMock).toHaveBeenLastCalledWith({
|
||||
filePath,
|
||||
fileId,
|
||||
line: 230,
|
||||
column: 1,
|
||||
matchLength: 0
|
||||
})
|
||||
})
|
||||
|
||||
it('scopes non-Markdown line reveals to the owner-qualified editor tab', async () => {
|
||||
setPlatform('Macintosh')
|
||||
const filePath = '/tmp/src/main.ts'
|
||||
const fileId = 'editor:wt-1:runtime-1:main.ts'
|
||||
openFileMock.mockImplementationOnce(() => {
|
||||
storeState.activeFileIdByWorktree['wt-1'] = fileId
|
||||
})
|
||||
|
||||
openDetectedFilePath(filePath, 42, 7, deps)
|
||||
await flushAsyncWork()
|
||||
await flushDoubleRaf()
|
||||
|
||||
expect(setMarkdownViewModeMock).not.toHaveBeenCalled()
|
||||
expect(setPendingEditorRevealMock).toHaveBeenLastCalledWith({
|
||||
filePath,
|
||||
fileId,
|
||||
line: 42,
|
||||
column: 7,
|
||||
matchLength: 0
|
||||
})
|
||||
})
|
||||
|
||||
it('uses the system default app for shift+cmd/ctrl-click file paths', async () => {
|
||||
setPlatform('Macintosh')
|
||||
|
||||
|
|
@ -428,6 +478,7 @@ describe('handleOscLink', () => {
|
|||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(1, null)
|
||||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(2, {
|
||||
filePath: '/tmp/src/main.ts',
|
||||
fileId: '/tmp/src/main.ts',
|
||||
line: 42,
|
||||
column: 7,
|
||||
matchLength: 0
|
||||
|
|
@ -526,6 +577,7 @@ describe('handleOscLink', () => {
|
|||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(1, null)
|
||||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(2, {
|
||||
filePath: 'C:/repo/src/index.ts',
|
||||
fileId: 'C:/repo/src/index.ts',
|
||||
line: 12,
|
||||
column: 3,
|
||||
matchLength: 0
|
||||
|
|
@ -587,6 +639,7 @@ describe('handleOscLink', () => {
|
|||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(1, null)
|
||||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(2, {
|
||||
filePath: '/tmp/test.txt',
|
||||
fileId: '/tmp/test.txt',
|
||||
line: 42,
|
||||
column: 1,
|
||||
matchLength: 0
|
||||
|
|
@ -629,6 +682,7 @@ describe('handleOscLink', () => {
|
|||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(1, null)
|
||||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(2, {
|
||||
filePath: '/tmp/test.txt',
|
||||
fileId: '/tmp/test.txt',
|
||||
line: 42,
|
||||
column: 7,
|
||||
matchLength: 0
|
||||
|
|
@ -662,6 +716,7 @@ describe('handleOscLink', () => {
|
|||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(1, null)
|
||||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(2, {
|
||||
filePath: '//server/Share/Repo/src/app.ts',
|
||||
fileId: '//server/Share/Repo/src/app.ts',
|
||||
line: 12,
|
||||
column: 3,
|
||||
matchLength: 0
|
||||
|
|
@ -721,6 +776,7 @@ describe('handleOscLink', () => {
|
|||
)
|
||||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(2, {
|
||||
filePath: '//wsl.localhost/Ubuntu/root/workspace/myrepo/README.md',
|
||||
fileId: '//wsl.localhost/Ubuntu/root/workspace/myrepo/README.md',
|
||||
line: 5,
|
||||
column: 3,
|
||||
matchLength: 0
|
||||
|
|
@ -752,6 +808,7 @@ describe('handleOscLink', () => {
|
|||
)
|
||||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(2, {
|
||||
filePath: '//wsl.localhost/Ubuntu/root/workspace/myrepo/README.md',
|
||||
fileId: '//wsl.localhost/Ubuntu/root/workspace/myrepo/README.md',
|
||||
line: 5,
|
||||
column: 3,
|
||||
matchLength: 0
|
||||
|
|
@ -1048,6 +1105,7 @@ describe('handleOscLink', () => {
|
|||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(1, null)
|
||||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(2, {
|
||||
filePath: '/tmp/src/second.ts',
|
||||
fileId: '/tmp/src/second.ts',
|
||||
line: 20,
|
||||
column: 3,
|
||||
matchLength: 0
|
||||
|
|
@ -1556,6 +1614,7 @@ describe('createFilePathLinkProvider range bounds', () => {
|
|||
)
|
||||
expect(setPendingEditorRevealMock).toHaveBeenNthCalledWith(2, {
|
||||
filePath: '//wsl.localhost/Ubuntu/root/workspace/myrepo/README.md',
|
||||
fileId: '//wsl.localhost/Ubuntu/root/workspace/myrepo/README.md',
|
||||
line: 5,
|
||||
column: 3,
|
||||
matchLength: 0
|
||||
|
|
@ -1774,6 +1833,7 @@ describe('createFilePathLinkProvider range bounds', () => {
|
|||
})
|
||||
expect(setPendingEditorRevealMock).toHaveBeenLastCalledWith({
|
||||
filePath: mappedPath,
|
||||
fileId: mappedPath,
|
||||
line: 5,
|
||||
column: 3,
|
||||
matchLength: 0
|
||||
|
|
|
|||
|
|
@ -32,6 +32,16 @@ export const TERMINAL_FILE_LINK_TAP_CONFORMANCE_CASES: TerminalFileLinkTapConfor
|
|||
tapText: 'Button',
|
||||
expected: { pathText: 'src/components/Button.tsx', line: 12, column: 7 }
|
||||
},
|
||||
{
|
||||
name: 'relative markdown path with line',
|
||||
lineText: 'documented in docs/terminal-scroll-intent-architecture.md:230',
|
||||
tapText: 'terminal-scroll',
|
||||
expected: {
|
||||
pathText: 'docs/terminal-scroll-intent-architecture.md',
|
||||
line: 230,
|
||||
column: null
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'tilde path',
|
||||
lineText: 'wrote ~/Documents/notes.md',
|
||||
|
|
|
|||
Loading…
Reference in New Issue