Skip to content

Commit

Permalink
Added command to only format selected comments
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreasArvidsson committed Feb 3, 2025
1 parent a6ad37b commit 1e91b9a
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 56 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "andreas-talon",
"displayName": "Andreas Talon",
"description": "VSCode extension used by Talon Voice",
"version": "3.66.0",
"version": "3.67.0",
"publisher": "AndreasArvidsson",
"license": "MIT",
"main": "./out/extension.js",
Expand Down
3 changes: 2 additions & 1 deletion src/commands/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ export const commandDescriptions = {
),
increment: visible("Edit", "Increment selected number.", undefined, "(value?: number)"),
decrement: visible("Edit", "Decrement selected number.", undefined, "(value?: number)"),
formatComments: makePrivate("Edit", "Format comments in active file."),
formatComments: makePrivate("Edit", "Format selected comments"),
formatAllComments: makePrivate("Edit", "Format comments in active file."),

// Navigation commands
openEditorAtIndex: visible(
Expand Down
26 changes: 9 additions & 17 deletions src/commands/formatComments/BaseCommentFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from "vscode";
import type { Selection, TextDocument } from "vscode";
import { Range } from "vscode";
import type { Change, CommentFormatter, CommentMatch, Line } from "./types";
import { isValidLine, parseTokens } from "./utils";
import { isValidLine, matchAll, parseTokens } from "./utils";

export abstract class BaseCommentFormatter implements CommentFormatter {
protected abstract regex: RegExp;
Expand All @@ -10,8 +11,7 @@ export abstract class BaseCommentFormatter implements CommentFormatter {

protected abstract parseMatch(match: RegExpExecArray): CommentMatch;

public parse(document: vscode.TextDocument): Change[] {
const matches = document.getText().matchAll(this.regex);
public parse(document: TextDocument, selections?: readonly Selection[]): Change[] {
const changes: Change[] = [];
const unprocessedLines: Line[] = [];

Expand All @@ -26,25 +26,17 @@ export abstract class BaseCommentFormatter implements CommentFormatter {
unprocessedLines.length = 0;
};

for (const match of matches) {
if (match.index == null) {
continue;
}
matchAll(document, selections, this.regex, (match, range) => {
const matchText = match[0];
const range = new vscode.Range(
document.positionAt(match.index),
document.positionAt(match.index + matchText.length)
);

const { text, isBlockComment } = this.parseMatch(match as RegExpExecArray);
const { text, isBlockComment } = this.parseMatch(match);
const indentation = matchText.slice(0, matchText.length - text.length);

if (isBlockComment) {
const newText = this.parseBlockComment(range, text, indentation);
if (newText != null) {
changes.push({ range, text: newText });
}
continue;
return;
}

// Non consecutive line comments. Process the previous lines.
Expand All @@ -57,7 +49,7 @@ export abstract class BaseCommentFormatter implements CommentFormatter {
}

unprocessedLines.push({ range, text, indentation });
}
});

// Process any remaining lines
if (unprocessedLines.length > 0) {
Expand All @@ -68,7 +60,7 @@ export abstract class BaseCommentFormatter implements CommentFormatter {
}

protected abstract parseBlockComment(
range: vscode.Range,
range: Range,
text: string,
indentation: string
): string | undefined;
Expand Down
26 changes: 7 additions & 19 deletions src/commands/formatComments/XmlFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from "vscode";
import type { Selection, TextDocument } from "vscode";
import { Range } from "vscode";
import type { Change, CommentFormatter, CommentMatch } from "./types";
import { isValidLine, parseTokens } from "./utils";
import { isValidLine, matchAll, parseTokens } from "./utils";

const prefix = "<!--";
const suffix = "-->";
Expand All @@ -10,28 +11,19 @@ export class XmlFormatter implements CommentFormatter {

constructor(private lineWidth: number) {}

public parse(document: vscode.TextDocument): Change[] {
const matches = document.getText().matchAll(this.regex);
public parse(document: TextDocument, selections?: readonly Selection[]): Change[] {
const changes: Change[] = [];

for (const match of matches) {
if (match.index == null) {
continue;
}
matchAll(document, selections, this.regex, (match, range) => {
const matchText = match[0];
const range = new vscode.Range(
document.positionAt(match.index),
document.positionAt(match.index + matchText.length)
);

const text = match[1];
const indentation = matchText.slice(0, matchText.length - text.length);

const newText = this.parseBlockComment(range, text, indentation);
if (newText != null) {
changes.push({ range, text: newText });
}
}
});

return changes;
}
Expand All @@ -42,11 +34,7 @@ export class XmlFormatter implements CommentFormatter {
return { text, isBlockComment };
}

private parseBlockComment(
range: vscode.Range,
text: string,
indentation: string
): string | undefined {
private parseBlockComment(range: Range, text: string, indentation: string): string | undefined {
// Extract the text between the "<!--" and "-->"
const textContent = text.slice(prefix.length, -suffix.length);
const linePrefix = "";
Expand Down
19 changes: 15 additions & 4 deletions src/commands/formatComments/formatComments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,32 @@ import { PythonFormatter } from "./PythonFormatter";
import type { CommentFormatter } from "./types";
import { XmlFormatter } from "./XmlFormatter";

interface Properties {
editor: vscode.TextEditor;
doSave?: boolean;
onlySelected?: boolean;
}

export function formatComments(): Promise<void> {
const editor = getActiveEditor();
return formatCommentsForEditor(editor);
return formatCommentsRunner({ editor: getActiveEditor(), onlySelected: true });
}

export function formatAllComments(): Promise<void> {
return formatCommentsRunner({ editor: getActiveEditor() });
}

export async function formatCommentsForEditor(editor: vscode.TextEditor, doSave = false) {
export async function formatCommentsRunner(properties: Properties): Promise<void> {
const { editor, doSave, onlySelected } = properties;
const { document } = editor;
const lineWidth = await getLineWidth(document);
const configuration = getFormatter(document.languageId, lineWidth);
const selections = onlySelected ? editor.selections : undefined;

if (configuration == null) {
return;
}

const changes = configuration.parse(document);
const changes = configuration.parse(document, selections);

if (changes.length === 0) {
return;
Expand Down
4 changes: 2 additions & 2 deletions src/commands/formatComments/registerFormatCommentsOnSave.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as vscode from "vscode";
import { formatCommentsForEditor } from "./formatComments";
import { configuration } from "../../util/configuration";
import { formatCommentsRunner } from "./formatComments";

export function registerFormatCommentsOnSave(): vscode.Disposable {
// onWillSaveTextDocument does not tree ge on "Save without formatting"
Expand All @@ -11,7 +11,7 @@ export function registerFormatCommentsOnSave(): vscode.Disposable {
) {
const editor = vscode.window.visibleTextEditors.find((e) => e.document === e.document);
if (editor != null) {
await formatCommentsForEditor(editor, true);
await formatCommentsRunner({ editor, doSave: true });
}
}
});
Expand Down
8 changes: 4 additions & 4 deletions src/commands/formatComments/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as vscode from "vscode";
import type { Range, Selection, TextDocument } from "vscode";

export interface CommentFormatter {
parse(document: vscode.TextDocument): Change[];
parse(document: TextDocument, selections?: readonly Selection[]): Change[];
}

export interface CommentMatch {
Expand All @@ -10,14 +10,14 @@ export interface CommentMatch {
}

export interface Change {
range: vscode.Range;
range: Range;
text: string;
}

export interface Line {
text: string;
indentation: string;
range: vscode.Range;
range: Range;
}

export interface Token {
Expand Down
38 changes: 38 additions & 0 deletions src/commands/formatComments/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Selection, TextDocument } from "vscode";
import { Range } from "vscode";
import type { Token } from "./types";

export const isValidLineRegex = /\w/;
Expand Down Expand Up @@ -49,3 +51,39 @@ function joinLine(parts: string[], indentation: string, linePrefix: string): str
}
return text.length > 0 ? `${indentation}${linePrefix} ${text}` : `${indentation}${linePrefix}`;
}

export function matchAll(
document: TextDocument,
selections: readonly Selection[] | undefined,
regex: RegExp,
callback: (match: RegExpExecArray, range: Range) => void
) {
// Ranges are always the full line. We don't format parts of a comment.
const ranges = selections?.map((selection) => {
if (selection.isSingleLine) {
return document.lineAt(selection.start.line).range;
}
return new Range(
selection.start.with(undefined, 0),
document.lineAt(selection.end.line).range.end
);
});

for (const range of ranges ?? [undefined]) {
const offset = range != null ? document.offsetAt(range.start) : 0;
const matches = document.getText(range).matchAll(regex);

for (const match of matches) {
if (match.index == null) {
continue;
}
callback(
match as RegExpExecArray,
new Range(
document.positionAt(offset + match.index),
document.positionAt(offset + match.index + match[0].length)
)
);
}
}
}
3 changes: 2 additions & 1 deletion src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { newFile } from "./files/newFile";
import { removeFile } from "./files/removeFile";
import { renameFile } from "./files/renameFile";
import { focusTab } from "./focusTab";
import { formatComments } from "./formatComments/formatComments";
import { formatAllComments, formatComments } from "./formatComments/formatComments";
import { formatSelectedFiles, formatWorkspaceFiles } from "./formatFiles";
import { generateRange } from "./generateRange";
import { goToLine } from "./goToLine";
Expand Down Expand Up @@ -50,6 +50,7 @@ export function registerCommands(
increment,
decrement,
formatComments,
formatAllComments,
// Navigation
openEditorAtIndex,
focusTab,
Expand Down
30 changes: 25 additions & 5 deletions src/test/formatComments.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { commands } from "vscode";
import { runTest } from "./testUtil/runTest";
import type { NumberSelection } from "./testUtil/test.types";

type Content = string | string[];

interface Test {
title: string;
pre: Content;
post: Content;
preSelections?: NumberSelection;
postSelections?: NumberSelection;
}

interface Language {
Expand Down Expand Up @@ -64,7 +67,17 @@ const templateLineTests: Test[] = [
}
];

const pythonLineTests = templateLineTests;
const pythonLineTests: Test[] = [
...templateLineTests,
{
title: "Line | Selection",
pre: "# a\n# b\n# c",
post: "# a b\n# c",
preSelections: [0, 0, 1, 0],
postSelections: [0, 0, 0, 4]
}
];

const cLineTests = createLineTests("//");
const luaLineTests = createLineTests("--");

Expand Down Expand Up @@ -166,18 +179,25 @@ const languages: Language[] = [
)
];

suite("Comment formatter", () => {
suite("Format comments", () => {
for (const language of languages) {
for (const fixture of language.tests) {
runTest({
title: `${language.id} | ${fixture.title}`,
callback: () => commands.executeCommand("andreas.formatComments"),
callback: () =>
commands.executeCommand(
fixture.preSelections
? "andreas.formatComments"
: "andreas.formatAllComments"
),
pre: {
language: language.id,
content: getContentString(fixture.pre)
content: getContentString(fixture.pre),
selections: fixture.preSelections
},
post: {
content: getContentString(fixture.post)
content: getContentString(fixture.post),
selections: fixture.postSelections
}
});
}
Expand Down

0 comments on commit 1e91b9a

Please sign in to comment.