Merge pull request #19056 from Giga-Bowser/fix-syntax-tree-crlf

fix: Properly handle CRLF line endings in the syntax tree view
This commit is contained in:
Lukas Wirth 2025-01-28 16:04:21 +00:00 committed by GitHub
commit 9f1ad04e72
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 183 additions and 78 deletions

File diff suppressed because one or more lines are too long

View file

@ -361,10 +361,7 @@ export function syntaxTreeReveal(): Cmd {
const activeEditor = vscode.window.activeTextEditor; const activeEditor = vscode.window.activeTextEditor;
if (activeEditor !== undefined) { if (activeEditor !== undefined) {
const start = activeEditor.document.positionAt(element.start); const newSelection = new vscode.Selection(element.range.start, element.range.end);
const end = activeEditor.document.positionAt(element.end);
const newSelection = new vscode.Selection(start, end);
activeEditor.selection = newSelection; activeEditor.selection = newSelection;
activeEditor.revealRange(newSelection); activeEditor.revealRange(newSelection);
@ -378,15 +375,12 @@ function elementToString(
depth: number = 0, depth: number = 0,
): string { ): string {
let result = " ".repeat(depth); let result = " ".repeat(depth);
const start = element.istart ?? element.start; const offsets = element.inner?.offsets ?? element.offsets;
const end = element.iend ?? element.end;
result += `${element.kind}@${start}..${end}`; result += `${element.kind}@${offsets.start}..${offsets.end}`;
if (element.type === "Token") { if (element.type === "Token") {
const startPosition = activeDocument.positionAt(element.start); const text = activeDocument.getText(element.range).replaceAll("\r\n", "\n");
const endPosition = activeDocument.positionAt(element.end);
const text = activeDocument.getText(new vscode.Range(startPosition, endPosition));
// JSON.stringify quotes and escapes the string for us. // JSON.stringify quotes and escapes the string for us.
result += ` ${JSON.stringify(text)}\n`; result += ` ${JSON.stringify(text)}\n`;
} else { } else {

View file

@ -384,9 +384,7 @@ export class Ctx implements RustAnalyzerExtensionApi {
return; return;
} }
const start = e.textEditor.document.offsetAt(selection.start); const result = this.syntaxTreeProvider?.getElementByRange(selection);
const end = e.textEditor.document.offsetAt(selection.end);
const result = this.syntaxTreeProvider?.getElementByRange(start, end);
if (result !== undefined) { if (result !== undefined) {
await this.syntaxTreeView?.reveal(result); await this.syntaxTreeView?.reveal(result);
} }

View file

@ -37,11 +37,7 @@ export class SyntaxTreeProvider implements vscode.TreeDataProvider<SyntaxElement
const editor = vscode.window.activeTextEditor; const editor = vscode.window.activeTextEditor;
if (editor !== undefined) { if (editor !== undefined) {
const start = editor.document.positionAt(element.start); const text = editor.document.getText(element.range);
const end = editor.document.positionAt(element.end);
const range = new vscode.Range(start, end);
const text = editor.document.getText(range);
item.tooltip = new vscode.MarkdownString().appendCodeblock(text, "rust"); item.tooltip = new vscode.MarkdownString().appendCodeblock(text, "rust");
} }
@ -74,14 +70,61 @@ export class SyntaxTreeProvider implements vscode.TreeDataProvider<SyntaxElement
if (editor && isRustEditor(editor)) { if (editor && isRustEditor(editor)) {
const params = { textDocument: { uri: editor.document.uri.toString() }, range: null }; const params = { textDocument: { uri: editor.document.uri.toString() }, range: null };
const fileText = await this.ctx.client.sendRequest(ra.viewSyntaxTree, params); const fileText = await this.ctx.client.sendRequest(ra.viewSyntaxTree, params);
this.root = JSON.parse(fileText, (_key, value: SyntaxElement) => { this.root = JSON.parse(fileText, (_key, value: RawElement): SyntaxElement => {
if (value.type === "Node") { if (value.type !== "Node" && value.type !== "Token") {
for (const child of value.children) { // This is something other than a RawElement.
child.parent = value; return value;
} }
const [startOffset, startLine, startCol] = value.start;
const [endOffset, endLine, endCol] = value.end;
const range = new vscode.Range(startLine, startCol, endLine, endCol);
const offsets = {
start: startOffset,
end: endOffset,
};
let inner;
if (value.istart && value.iend) {
const [istartOffset, istartLine, istartCol] = value.istart;
const [iendOffset, iendLine, iendCol] = value.iend;
inner = {
offsets: {
start: istartOffset,
end: iendOffset,
},
range: new vscode.Range(istartLine, istartCol, iendLine, iendCol),
};
} }
return value; if (value.type === "Node") {
const result = {
type: value.type,
kind: value.kind,
offsets,
range,
inner,
children: value.children,
parent: undefined,
document: editor.document,
};
for (const child of result.children) {
child.parent = result;
}
return result;
} else {
return {
type: value.type,
kind: value.kind,
offsets,
range,
inner,
parent: undefined,
document: editor.document,
};
}
}); });
} else { } else {
this.root = undefined; this.root = undefined;
@ -90,14 +133,14 @@ export class SyntaxTreeProvider implements vscode.TreeDataProvider<SyntaxElement
this._onDidChangeTreeData.fire(); this._onDidChangeTreeData.fire();
} }
getElementByRange(start: number, end: number): SyntaxElement | undefined { getElementByRange(target: vscode.Range): SyntaxElement | undefined {
if (this.root === undefined) { if (this.root === undefined) {
return undefined; return undefined;
} }
let result: SyntaxElement = this.root; let result: SyntaxElement = this.root;
if (this.root.start === start && this.root.end === end) { if (this.root.range.isEqual(target)) {
return result; return result;
} }
@ -105,9 +148,9 @@ export class SyntaxTreeProvider implements vscode.TreeDataProvider<SyntaxElement
outer: while (true) { outer: while (true) {
for (const child of children) { for (const child of children) {
if (child.start <= start && child.end >= end) { if (child.range.contains(target)) {
result = child; result = child;
if (start === end && start === child.end) { if (target.isEmpty && target.start === child.range.end) {
// When the cursor is on the very end of a token, // When the cursor is on the very end of a token,
// we assume the user wants the next token instead. // we assume the user wants the next token instead.
continue; continue;
@ -136,31 +179,72 @@ export class SyntaxTreeProvider implements vscode.TreeDataProvider<SyntaxElement
export type SyntaxNode = { export type SyntaxNode = {
type: "Node"; type: "Node";
kind: string; kind: string;
range: vscode.Range;
offsets: {
start: number; start: number;
end: number; end: number;
istart?: number; };
iend?: number; /** This element's position within a Rust string literal, if it's inside of one. */
inner?: {
range: vscode.Range;
offsets: {
start: number;
end: number;
};
};
children: SyntaxElement[]; children: SyntaxElement[];
parent?: SyntaxElement; parent?: SyntaxElement;
document: vscode.TextDocument;
}; };
type SyntaxToken = { type SyntaxToken = {
type: "Token"; type: "Token";
kind: string; kind: string;
range: vscode.Range;
offsets: {
start: number; start: number;
end: number; end: number;
istart?: number; };
iend?: number; /** This element's position within a Rust string literal, if it's inside of one. */
inner?: {
range: vscode.Range;
offsets: {
start: number;
end: number;
};
};
parent?: SyntaxElement; parent?: SyntaxElement;
document: vscode.TextDocument;
}; };
export type SyntaxElement = SyntaxNode | SyntaxToken; export type SyntaxElement = SyntaxNode | SyntaxToken;
type RawNode = {
type: "Node";
kind: string;
start: [number, number, number];
end: [number, number, number];
istart?: [number, number, number];
iend?: [number, number, number];
children: SyntaxElement[];
};
type RawToken = {
type: "Token";
kind: string;
start: [number, number, number];
end: [number, number, number];
istart?: [number, number, number];
iend?: [number, number, number];
};
type RawElement = RawNode | RawToken;
export class SyntaxTreeItem extends vscode.TreeItem { export class SyntaxTreeItem extends vscode.TreeItem {
constructor(private readonly element: SyntaxElement) { constructor(private readonly element: SyntaxElement) {
super(element.kind); super(element.kind);
const icon = getIcon(element.kind); const icon = getIcon(this.element.kind);
if (element.type === "Node") { if (this.element.type === "Node") {
this.contextValue = "syntaxNode"; this.contextValue = "syntaxNode";
this.iconPath = icon ?? new vscode.ThemeIcon("list-tree"); this.iconPath = icon ?? new vscode.ThemeIcon("list-tree");
this.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; this.collapsibleState = vscode.TreeItemCollapsibleState.Expanded;
@ -170,11 +254,9 @@ export class SyntaxTreeItem extends vscode.TreeItem {
this.collapsibleState = vscode.TreeItemCollapsibleState.None; this.collapsibleState = vscode.TreeItemCollapsibleState.None;
} }
if (element.istart !== undefined && element.iend !== undefined) { const offsets = this.element.inner?.offsets ?? this.element.offsets;
this.description = `${this.element.istart}..${this.element.iend}`;
} else { this.description = `${offsets.start}..${offsets.end}`;
this.description = `${this.element.start}..${this.element.end}`;
}
} }
} }