Skip to content

Commit e682c14

Browse files
authored
Merge pull request microsoft#186087 from microsoft/tyriar/182878
Support git diff @@ ... to-file-range @@ links
2 parents 11e3fd2 + bfbbb12 commit e682c14

File tree

2 files changed

+102
-5
lines changed

2 files changed

+102
-5
lines changed

src/vs/workbench/contrib/terminalContrib/links/browser/terminalMultiLineLinkDetector.ts

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,22 @@ const enum Constants {
2525
MaxResolvedLinkLength = 1024,
2626
}
2727

28-
const candidateMatchers = [
28+
const lineNumberPrefixMatchers = [
2929
// Ripgrep:
30+
// /some/file
3031
// 16:searchresult
3132
// 16: searchresult
3233
// Eslint:
33-
// 16:5 error ...
34-
/\s*(?<link>(?<line>\d+):(?<col>\d+)?)/
34+
// /some/file
35+
// 16:5 error ...
36+
/ *(?<link>(?<line>\d+):(?<col>\d+)?)/
37+
];
38+
39+
const gitDiffMatchers = [
40+
// --- a/some/file
41+
// +++ b/some/file
42+
// @@ -8,11 +8,11 @@ file content...
43+
/^(?<link>@@ .+ \+(?<toFileLine>\d+),(?<toFileCount>\d+) @@)/
3544
];
3645

3746
export class TerminalMultiLineLinkDetector implements ITerminalLinkDetector {
@@ -66,7 +75,7 @@ export class TerminalMultiLineLinkDetector implements ITerminalLinkDetector {
6675

6776
// Match against the fallback matchers which are mainly designed to catch paths with spaces
6877
// that aren't possible using the regular mechanism.
69-
for (const matcher of candidateMatchers) {
78+
for (const matcher of lineNumberPrefixMatchers) {
7079
const match = text.match(matcher);
7180
const group = match?.groups;
7281
if (!group) {
@@ -130,7 +139,7 @@ export class TerminalMultiLineLinkDetector implements ITerminalLinkDetector {
130139
uri: linkStat.uri,
131140
selection: {
132141
startLineNumber: parseInt(line),
133-
startColumn: col ? parseInt(col) : 0
142+
startColumn: col ? parseInt(col) : 1
134143
},
135144
disableTrimColon: true,
136145
bufferRange: bufferRange,
@@ -144,6 +153,88 @@ export class TerminalMultiLineLinkDetector implements ITerminalLinkDetector {
144153
}
145154
}
146155

156+
if (links.length === 0) {
157+
for (const matcher of gitDiffMatchers) {
158+
const match = text.match(matcher);
159+
const group = match?.groups;
160+
if (!group) {
161+
continue;
162+
}
163+
const link = group?.link;
164+
const toFileLine = group?.toFileLine;
165+
const toFileCount = group?.toFileCount;
166+
if (!link || toFileLine === undefined) {
167+
continue;
168+
}
169+
170+
// Don't try resolve any links of excessive length
171+
if (link.length > Constants.MaxResolvedLinkLength) {
172+
continue;
173+
}
174+
175+
this._logService.trace('terminalMultiLineLinkDetector#detect candidate', link);
176+
177+
178+
// Scan up looking for the first line that could be a path
179+
let possiblePath: string | undefined;
180+
for (let index = startLine - 1; index >= 0; index--) {
181+
// Ignore lines that aren't at the beginning of a wrapped line
182+
if (this.xterm.buffer.active.getLine(index)!.isWrapped) {
183+
continue;
184+
}
185+
const text = getXtermLineContent(this.xterm.buffer.active, index, index, this.xterm.cols);
186+
const match = text.match(/\+\+\+ b\/(?<path>.+)/);
187+
if (match) {
188+
possiblePath = match.groups?.path;
189+
break;
190+
}
191+
}
192+
if (!possiblePath) {
193+
continue;
194+
}
195+
196+
// Check if the first non-matching line is an absolute or relative link
197+
const linkStat = await this._linkResolver.resolveLink(this._processManager, possiblePath);
198+
if (linkStat) {
199+
let type: TerminalBuiltinLinkType;
200+
if (linkStat.isDirectory) {
201+
if (this._isDirectoryInsideWorkspace(linkStat.uri)) {
202+
type = TerminalBuiltinLinkType.LocalFolderInWorkspace;
203+
} else {
204+
type = TerminalBuiltinLinkType.LocalFolderOutsideWorkspace;
205+
}
206+
} else {
207+
type = TerminalBuiltinLinkType.LocalFile;
208+
}
209+
210+
// Convert the link to the buffer range
211+
const bufferRange = convertLinkRangeToBuffer(lines, this.xterm.cols, {
212+
startColumn: 1,
213+
startLineNumber: 1,
214+
endColumn: 1 + link.length,
215+
endLineNumber: 1
216+
}, startLine);
217+
218+
const simpleLink: ITerminalSimpleLink = {
219+
text: link,
220+
uri: linkStat.uri,
221+
selection: {
222+
startLineNumber: parseInt(toFileLine),
223+
startColumn: 1,
224+
endLineNumber: parseInt(toFileLine) + parseInt(toFileCount)
225+
},
226+
bufferRange: bufferRange,
227+
type
228+
};
229+
this._logService.trace('terminalMultiLineLinkDetector#detect verified link', simpleLink);
230+
links.push(simpleLink);
231+
232+
// Break on the first match
233+
break;
234+
}
235+
}
236+
}
237+
147238
return links;
148239
}
149240

src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalMultiLineLinkDetector.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,18 @@ interface LinkFormatInfo {
8484
}
8585

8686
const supportedLinkFormats: LinkFormatInfo[] = [
87+
// 5: file content... [#181837]
88+
// 5:3 error [#181837]
8789
{ urlFormat: '{0}\r\n{1}:foo', line: '5' },
8890
{ urlFormat: '{0}\r\n{1}: foo', line: '5' },
8991
{ urlFormat: '{0}\r\n5:another link\r\n{1}:{2} foo', line: '5', column: '3' },
9092
{ urlFormat: '{0}\r\n {1}:{2} foo', line: '5', column: '3' },
9193
{ urlFormat: '{0}\r\n 5:6 error another one\r\n {1}:{2} error', line: '5', column: '3' },
9294
{ urlFormat: `{0}\r\n 5:6 error ${'a'.repeat(80)}\r\n {1}:{2} error`, line: '5', column: '3' },
95+
96+
// @@ ... <to-file-range> @@ content... [#182878] (tests check the entire line, so they don't include the line content at the end of the last @@)
97+
{ urlFormat: '+++ b/{0}\r\n@@ -7,6 +{1},7 @@', line: '5' },
98+
{ urlFormat: '+++ b/{0}\r\n@@ -1,1 +1,1 @@\r\nfoo\r\nbar\r\n@@ -7,6 +{1},7 @@', line: '5' },
9399
];
94100

95101
suite('Workbench - TerminalMultiLineLinkDetector', () => {

0 commit comments

Comments
 (0)