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:
Kaynan Sampaio de Camargo 2026-07-24 00:39:37 -07:00 committed by GitHub
parent d1ccfcff40
commit d50ea090cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 82 additions and 2 deletions

View File

@ -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

View File

@ -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

View File

@ -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',