diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b1211e..2633bd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- Issue #111: Added context menu option to prettify table at cursor without selection. ### Fixed - Issue #106: Fixed table prettification breaking when cells contain zero-width characters. diff --git a/README.md b/README.md index 1d2d341..d2d44c7 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Makes tables more readable for humans. Compatible with the Markdown writer plugi The extension is available for markdown language mode. It can either prettify a selection (`Format Selection`) or the entire document (`Format Document`). A VSCode command called `Prettify markdown tables` is also available to format the currently opened document. +Right-click on a table to access the context menu option `Prettify markdown table at cursor` for formatting individual tables without selection. ### Configurable settings: - The maximum texth length of a selection/entire document to consider for formatting. Default: 1M chars (limit does not apply from CLI or NPM). diff --git a/package.json b/package.json index e6947ea..b7547e6 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,10 @@ { "command": "markdownTablePrettify.prettifyTables", "title": "Prettify markdown tables" + }, + { + "command": "markdownTablePrettify.prettifyTableAtCursor", + "title": "Prettify markdown table at cursor" } ], "keybindings": [ @@ -67,7 +71,16 @@ "mac": "cmd+alt+m", "when": "editorTextFocus && !editorReadonly && !inCompositeEditor" } - ] + ], + "menus": { + "editor/context": [ + { + "command": "markdownTablePrettify.prettifyTableAtCursor", + "when": "markdownTablePrettify.hasTableAtCursor && editorTextFocus && !editorReadonly && !inCompositeEditor", + "group": "1_modification@2" + } + ] + } }, "capabilities": { "documentFormattingProvider": "true" diff --git a/src/extension/extension.ts b/src/extension/extension.ts index a472895..b024912 100644 --- a/src/extension/extension.ts +++ b/src/extension/extension.ts @@ -1,6 +1,7 @@ 'use strict'; import * as vscode from 'vscode'; -import { getSupportLanguageIds, getDocumentRangePrettyfier, getDocumentPrettyfier, getDocumentPrettyfierCommand, invalidateCache } from './prettyfierFactory'; +import { getSupportLanguageIds, getDocumentRangePrettyfier, getDocumentPrettyfier, getDocumentPrettyfierCommand, getTableAtCursorPrettyfier, invalidateCache } from './prettyfierFactory'; +import { TableAtCursorContextKeyUpdater } from './tableAtCursorContextKeyUpdater'; // This method is called when the extension is activated. // The extension is activated the very first time the command is executed. @@ -23,11 +24,35 @@ export function activate(context: vscode.ExtensionContext): void { ); } + const tableAtCursorContextKey = "markdownTablePrettify.hasTableAtCursor"; + const contextKeyUpdater = new TableAtCursorContextKeyUpdater( + supportedLanguageIds, + tableAtCursorContextKey, + getTableAtCursorPrettyfier(), + vscode.commands.executeCommand + ); + + context.subscriptions.push( + vscode.window.onDidChangeActiveTextEditor(editor => void contextKeyUpdater.update(editor)), + vscode.window.onDidChangeTextEditorSelection(event => void contextKeyUpdater.update(event.textEditor)) + ); + + void contextKeyUpdater.update(vscode.window.activeTextEditor); + const command = "markdownTablePrettify.prettifyTables"; context.subscriptions.push( - vscode.commands.registerTextEditorCommand(command, textEditor => { - if (supportedLanguageIds.indexOf(textEditor.document.languageId) >= 0) - getDocumentPrettyfierCommand().prettifyDocument(textEditor); + vscode.commands.registerTextEditorCommand(command, async textEditor => { + if (supportedLanguageIds.includes(textEditor.document.languageId)) + await getDocumentPrettyfierCommand().prettifyDocument(textEditor); + }) + ); + + const formatTableCommand = "markdownTablePrettify.prettifyTableAtCursor"; + context.subscriptions.push( + vscode.commands.registerTextEditorCommand(formatTableCommand, async textEditor => { + if (supportedLanguageIds.includes(textEditor.document.languageId)) { + await getTableAtCursorPrettyfier().prettifyTableAtCursor(textEditor); + } }) ); } diff --git a/src/extension/prettyfierFactory.ts b/src/extension/prettyfierFactory.ts index 0a3657e..4ae2822 100644 --- a/src/extension/prettyfierFactory.ts +++ b/src/extension/prettyfierFactory.ts @@ -20,14 +20,17 @@ import { SelectionInterpreter } from '../modelFactory/selectionInterpreter'; import { PadCalculatorSelector } from '../padCalculation/padCalculatorSelector'; import { AlignmentMarkerStrategy } from '../viewModelFactories/alignmentMarking'; import { MultiTablePrettyfier } from '../prettyfiers/multiTablePrettyfier'; +import { TableAtCursorPrettyfier } from "../prettyfiers/tableAtCursorPrettyfier"; import { SingleTablePrettyfier } from '../prettyfiers/singleTablePrettyfier'; import { TableStringWriter } from "../writers/tableStringWriter"; import { ValuePaddingProvider } from '../writers/valuePaddingProvider'; let cachedMultiTablePrettyfier: MultiTablePrettyfier | null = null; +let cachedTableAtCursorPrettyfier: TableAtCursorPrettyfier | null = null; export function invalidateCache() { cachedMultiTablePrettyfier = null; + cachedTableAtCursorPrettyfier = null; } export function getSupportLanguageIds() { @@ -64,6 +67,23 @@ function getMultiTablePrettyfier(): MultiTablePrettyfier { return cachedMultiTablePrettyfier; } +export function getTableAtCursorPrettyfier(): TableAtCursorPrettyfier { + if (cachedTableAtCursorPrettyfier) { + return cachedTableAtCursorPrettyfier; + } + + const loggers = getLoggers(); + const sizeLimitChecker = getSizeLimitChecker(loggers); + const columnPadding = getConfigurationValue("columnPadding", 0); + + cachedTableAtCursorPrettyfier = new TableAtCursorPrettyfier( + new TableFinder(new TableValidator(new SelectionInterpreter(true))), + getSingleTablePrettyfier(loggers, sizeLimitChecker, columnPadding) + ); + + return cachedTableAtCursorPrettyfier; +} + function getSingleTablePrettyfier(loggers: ILogger[], sizeLimitCheker: ConfigSizeLimitChecker, columnPadding: number): SingleTablePrettyfier { return new SingleTablePrettyfier( new TableFactory( diff --git a/src/extension/tableAtCursorContextKeyUpdater.ts b/src/extension/tableAtCursorContextKeyUpdater.ts new file mode 100644 index 0000000..c359439 --- /dev/null +++ b/src/extension/tableAtCursorContextKeyUpdater.ts @@ -0,0 +1,46 @@ +import * as vscode from 'vscode'; +import { Document } from '../models/doc/document'; +import { TableAtCursorPrettyfier } from '../prettyfiers/tableAtCursorPrettyfier'; + +export class TableAtCursorContextKeyUpdater { + private _lastEditorUri: string | null = null; + private _lastDocumentVersion: number | null = null; + private _lastLine: number | null = null; + + constructor( + private readonly _supportedLanguageIds: string[], + private readonly _contextKey: string, + private readonly _tableAtCursorPrettyfier: TableAtCursorPrettyfier, + private readonly _executeCommand: (command: string, ...args: any[]) => Thenable + ) { } + + public async update(editor?: vscode.TextEditor): Promise { + if (!editor || !this._supportedLanguageIds.includes(editor.document.languageId)) { + this._lastEditorUri = null; + this._lastDocumentVersion = null; + this._lastLine = null; + await this._executeCommand('setContext', this._contextKey, false); + return; + } + + const currentEditorUri = editor.document.uri.toString(); + const currentDocumentVersion = editor.document.version; + const currentLine = editor.selection.active.line; + + if (this._lastEditorUri === currentEditorUri + && this._lastDocumentVersion === currentDocumentVersion + && this._lastLine === currentLine) { + return; + } + + this._lastEditorUri = currentEditorUri; + this._lastDocumentVersion = currentDocumentVersion; + this._lastLine = currentLine; + + const hasTableAtCursor = this._tableAtCursorPrettyfier.hasTableAtCursor( + new Document(editor.document.getText()), + currentLine); + + await this._executeCommand('setContext', this._contextKey, hasTableAtCursor); + } +} diff --git a/src/extension/tableDocumentPrettyfierCommand.ts b/src/extension/tableDocumentPrettyfierCommand.ts index 44de745..8ca48d9 100644 --- a/src/extension/tableDocumentPrettyfierCommand.ts +++ b/src/extension/tableDocumentPrettyfierCommand.ts @@ -7,10 +7,10 @@ export class TableDocumentPrettyfierCommand { private readonly _multiTablePrettyfier: MultiTablePrettyfier ) { } - public prettifyDocument(editor: vscode.TextEditor) { + public async prettifyDocument(editor: vscode.TextEditor): Promise { const formattedDocument: string = this._multiTablePrettyfier.formatTables(editor.document.getText()); - editor.edit(textEditorEdit => { + await editor.edit(textEditorEdit => { textEditorEdit.replace(new vscode.Range( new vscode.Position(0, 0), new vscode.Position(editor.document.lineCount - 1, Number.MAX_SAFE_INTEGER) diff --git a/src/prettyfiers/tableAtCursorPrettyfier.ts b/src/prettyfiers/tableAtCursorPrettyfier.ts new file mode 100644 index 0000000..840ce74 --- /dev/null +++ b/src/prettyfiers/tableAtCursorPrettyfier.ts @@ -0,0 +1,45 @@ +import * as vscode from "vscode"; +import { TableFinder } from "../tableFinding/tableFinder"; +import { SingleTablePrettyfier } from "../prettyfiers/singleTablePrettyfier"; +import { Document } from "../models/doc/document"; +import { Range } from "../models/doc/range"; + +export class TableAtCursorPrettyfier { + + constructor( + private readonly _tableFinder: TableFinder, + private readonly _singleTablePrettyfier: SingleTablePrettyfier + ) { } + + public async prettifyTableAtCursor(editor: vscode.TextEditor): Promise { + const cursorLine = editor.selection.active.line; + const document = new Document(editor.document.getText()); + + const tableRange = this.findTableRangeAtLine(document, cursorLine); + if (tableRange == null) { + return false; + } + + const formattedTable = this._singleTablePrettyfier.prettifyTable(document, tableRange); + + await editor.edit(editBuilder => { + editBuilder.replace( + new vscode.Range( + new vscode.Position(tableRange.startLine, 0), + editor.document.lineAt(tableRange.endLine).range.end + ), + formattedTable + ); + }); + + return true; + } + + public hasTableAtCursor(document: Document, cursorLine: number): boolean { + return this.findTableRangeAtLine(document, cursorLine) != null; + } + + private findTableRangeAtLine(document: Document, cursorLine: number): Range | null { + return this._tableFinder.getRangeContainingLine(document, cursorLine); + } +} \ No newline at end of file diff --git a/src/tableFinding/tableFinder.ts b/src/tableFinding/tableFinder.ts index 4d69d9c..29b1ac3 100644 --- a/src/tableFinding/tableFinder.ts +++ b/src/tableFinding/tableFinder.ts @@ -23,11 +23,7 @@ export class TableFinder { } if (!isInIgnoreBlock) { - const isValidSeparatorRow = this._tableValidator.lineIsValidSeparator(document.lines[rowIndex].value); - const nextRangeResult: { range: Range | null, ignoreBlockStarted: boolean } = isValidSeparatorRow - ? this.getNextValidTableRange(document, rowIndex) - : { range: null, ignoreBlockStarted: isInIgnoreBlock}; - + const nextRangeResult = this.getRangeAtSeparatorRow(document, rowIndex); isInIgnoreBlock = nextRangeResult.ignoreBlockStarted; if (nextRangeResult.range != null) { @@ -40,6 +36,73 @@ export class TableFinder { return null; } + public getRangeContainingLine(document: Document, lineIndex: number): Range | null { + if (lineIndex < 0 || lineIndex >= document.lines.length) { + return null; + } + + // search locally around the cursor + const trySeparator = (separatorRowIndex: number): Range | null => { + if (this.isLineInsideIgnoreBlock(document, separatorRowIndex)) { + return null; + } + + const range = this.getRangeAtSeparatorRow(document, separatorRowIndex).range; + return range != null && range.startLine <= lineIndex && lineIndex <= range.endLine + ? range + : null; + }; + + const initialRange = trySeparator(lineIndex); + if (initialRange != null) { + return initialRange; + } + + let offset = 1; + while (lineIndex - offset >= 0 || lineIndex + offset < document.lines.length) { + if (lineIndex - offset >= 0) { + const range = trySeparator(lineIndex - offset); + if (range != null) { + return range; + } + } + + if (lineIndex + offset < document.lines.length) { + const range = trySeparator(lineIndex + offset); + if (range != null) { + return range; + } + } + + offset++; + } + + return null; + } + + private isLineInsideIgnoreBlock(document: Document, lineIndex: number): boolean { + let ignoreBlockStarted = false; + + for (let index = 0; index <= lineIndex && index < document.lines.length; index++) { + const trimmedLine = document.lines[index].value.trim(); + if (trimmedLine == this._ignoreStart) { + ignoreBlockStarted = true; + } else if (trimmedLine == this._ignoreEnd) { + ignoreBlockStarted = false; + } + } + + return ignoreBlockStarted; + } + + private getRangeAtSeparatorRow(document: Document, separatorRowIndex: number): { range: Range | null, ignoreBlockStarted: boolean } { + if (!this._tableValidator.lineIsValidSeparator(document.lines[separatorRowIndex].value)) { + return { range: null, ignoreBlockStarted: false }; + } + + return this.getNextValidTableRange(document, separatorRowIndex); + } + private getNextValidTableRange(document: Document, separatorRowIndex: number): { range: Range | null, ignoreBlockStarted: boolean} { let firstTableFileRow = separatorRowIndex - 1; let lastTableFileRow = separatorRowIndex; diff --git a/test/unitTests/extension/tableAtCursorContextKeyUpdater.test.ts b/test/unitTests/extension/tableAtCursorContextKeyUpdater.test.ts new file mode 100644 index 0000000..1cae76d --- /dev/null +++ b/test/unitTests/extension/tableAtCursorContextKeyUpdater.test.ts @@ -0,0 +1,100 @@ +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import { IMock, Mock, It } from 'typemoq'; +import { MarkdownTextDocumentStub } from '../../stubs/markdownTextDocumentStub'; +import { TableAtCursorContextKeyUpdater } from '../../../src/extension/tableAtCursorContextKeyUpdater'; +import { TableAtCursorPrettyfier } from '../../../src/prettyfiers/tableAtCursorPrettyfier'; + +suite('TableAtCursorContextKeyUpdater tests', () => { + let _tableAtCursorPrettyfier: IMock; + let executedCommands: Array<{ command: string, args: any[] }>; + let executeCommand: (command: string, ...args: any[]) => Thenable; + + setup(() => { + _tableAtCursorPrettyfier = Mock.ofType(); + executedCommands = []; + executeCommand = (command: string, ...args: any[]) => { + executedCommands.push({ command, args }); + return Promise.resolve(true); + }; + }); + + test('update() sets context false for unsupported language and clears cache', async () => { + const sut = createSut(); + const document = new MarkdownTextDocumentStub('|A|B|\n|-|-|\n|1|2|'); + document.languageId = 'text'; + document.version = 1; + document.uri = vscode.Uri.file('test.md'); + + const textEditor = Mock.ofType(); + textEditor.setup(e => e.document).returns(() => document); + textEditor.setup(e => e.selection).returns(() => new vscode.Selection(0, 0, 0, 0)); + + await sut.update(textEditor.object); + + assert.strictEqual(executedCommands.length, 1); + assert.strictEqual(executedCommands[0].command, 'setContext'); + assert.deepStrictEqual(executedCommands[0].args, ['markdownTablePrettify.hasTableAtCursor', false]); + }); + + test('update() caches repeated editor state and does not call setContext again', async () => { + const sut = createSut(); + const document = new MarkdownTextDocumentStub('|A|B|\n|-|-|\n|1|2|'); + document.languageId = 'markdown'; + document.version = 1; + document.uri = vscode.Uri.file('test.md'); + + const selection = new vscode.Selection(0, 0, 0, 0); + const textEditor = Mock.ofType(); + textEditor.setup(e => e.document).returns(() => document); + textEditor.setup(e => e.selection).returns(() => selection); + + _tableAtCursorPrettyfier + .setup(prettifier => prettifier.hasTableAtCursor(It.isAny(), 0)) + .returns(() => true); + + await sut.update(textEditor.object); + await sut.update(textEditor.object); + + assert.strictEqual(executedCommands.length, 1); + assert.strictEqual(executedCommands[0].command, 'setContext'); + assert.deepStrictEqual(executedCommands[0].args, ['markdownTablePrettify.hasTableAtCursor', true]); + }); + + test('update() with changed line calls setContext again', async () => { + const sut = createSut(); + const document = new MarkdownTextDocumentStub('|A|B|\n|-|-|\n|1|2|'); + document.languageId = 'markdown'; + document.version = 1; + document.uri = vscode.Uri.file('test.md'); + + const textEditor = Mock.ofType(); + textEditor.setup(e => e.document).returns(() => document); + textEditor.setup(e => e.selection).returns(() => new vscode.Selection(0, 0, 0, 0)); + + _tableAtCursorPrettyfier + .setup(prettifier => prettifier.hasTableAtCursor(It.isAny(), 0)) + .returns(() => true); + + await sut.update(textEditor.object); + + textEditor.setup(e => e.selection).returns(() => new vscode.Selection(1, 0, 1, 0)); + _tableAtCursorPrettyfier + .setup(prettifier => prettifier.hasTableAtCursor(It.isAny(), 1)) + .returns(() => false); + + await sut.update(textEditor.object); + + assert.strictEqual(executedCommands.length, 2); + assert.deepStrictEqual(executedCommands[1].args, ['markdownTablePrettify.hasTableAtCursor', false]); + }); + + function createSut(): TableAtCursorContextKeyUpdater { + return new TableAtCursorContextKeyUpdater( + ['markdown'], + 'markdownTablePrettify.hasTableAtCursor', + _tableAtCursorPrettyfier.object, + executeCommand + ); + } +}); diff --git a/test/unitTests/extension/tableDocumentPrettyfierCommand.test.ts b/test/unitTests/extension/tableDocumentPrettyfierCommand.test.ts index e9bf408..29459e4 100644 --- a/test/unitTests/extension/tableDocumentPrettyfierCommand.test.ts +++ b/test/unitTests/extension/tableDocumentPrettyfierCommand.test.ts @@ -12,23 +12,24 @@ suite("TableDocumentPrettyfierCommand tests", () => { _multiTablePrettyfier = Mock.ofType(); }); - test("prettifyDocument() calls MultiTablePrettyfier and edit()", () => { + test("prettifyDocument() calls MultiTablePrettyfier and edit()", async () => { const sut = createSut(); const input = Array(10).fill("hello world").join("\n"); const expectedResult = Array(10).fill("expected result").join("\n"); const textEditor = Mock.ofType(); const document = new MarkdownTextDocumentStub(input); textEditor.setup(e => e.document).returns(() => document); + textEditor.setup(e => e.edit(It.isAny())).returns(() => Promise.resolve(true)); // Note: due to a limitation of the MarkdownTextDocumentStub with OS line endings, we use `It.isAny()` instead of `input`. _multiTablePrettyfier.setup(multiTablePrettyfier => multiTablePrettyfier.formatTables(It.isAny())).returns(() => expectedResult) - sut.prettifyDocument(textEditor.object); + await sut.prettifyDocument(textEditor.object); textEditor.verify(e => e.edit(It.isAny()), Times.once()); _multiTablePrettyfier.verify(multiTablePrettyfier => multiTablePrettyfier.formatTables(It.isAny()), Times.once()); }); - test("prettifyDocument() for non-markdown documents it still calls MultiTablePrettyfier and edit()", () => { + test("prettifyDocument() for non-markdown documents it still calls MultiTablePrettyfier and edit()", async () => { const sut = createSut(); const input = Array(10).fill("hello world").join("\n"); const expectedResult = Array(10).fill("expected result").join("\n"); @@ -36,9 +37,10 @@ suite("TableDocumentPrettyfierCommand tests", () => { const document = new MarkdownTextDocumentStub(input); document.languageId = "text"; textEditor.setup(e => e.document).returns(() => document); + textEditor.setup(e => e.edit(It.isAny())).returns(() => Promise.resolve(true)); _multiTablePrettyfier.setup(multiTablePrettyfier => multiTablePrettyfier.formatTables(It.isAny())).returns(() => expectedResult) - sut.prettifyDocument(textEditor.object); + await sut.prettifyDocument(textEditor.object); textEditor.verify(e => e.edit(It.isAny()), Times.once()); _multiTablePrettyfier.verify(multiTablePrettyfier => multiTablePrettyfier.formatTables(It.isAny()), Times.once()); diff --git a/test/unitTests/prettyfiers/tableAtCursorPrettyfier.test.ts b/test/unitTests/prettyfiers/tableAtCursorPrettyfier.test.ts new file mode 100644 index 0000000..406d6df --- /dev/null +++ b/test/unitTests/prettyfiers/tableAtCursorPrettyfier.test.ts @@ -0,0 +1,93 @@ +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import { IMock, Mock, It, Times } from 'typemoq'; +import { MarkdownTextDocumentStub } from '../../stubs/markdownTextDocumentStub'; +import { Document } from '../../../src/models/doc/document'; +import { Range } from '../../../src/models/doc/range'; +import { TableAtCursorPrettyfier } from '../../../src/prettyfiers/tableAtCursorPrettyfier'; +import { TableFinder } from '../../../src/tableFinding/tableFinder'; +import { SingleTablePrettyfier } from '../../../src/prettyfiers/singleTablePrettyfier'; + +suite('TableAtCursorPrettyfier tests', () => { + + let _tableFinder: IMock; + let _singleTablePrettyfier: IMock; + + setup(() => { + _tableFinder = Mock.ofType(); + _singleTablePrettyfier = Mock.ofType(); + }); + + test('hasTableAtCursor() returns true when a table range contains the cursor', () => { + const sut = createSut(); + const document = new Document('|A|B|\n|-|-|\n|1|2|'); + + _tableFinder + .setup(tableFinder => tableFinder.getRangeContainingLine(It.isAny(), 1)) + .returns(() => new Range(0, 2)); + + const result = sut.hasTableAtCursor(document, 1); + + assert.strictEqual(result, true); + }); + + test('hasTableAtCursor() returns false when no table range is found', () => { + const sut = createSut(); + const document = new Document('hello\nworld'); + + _tableFinder + .setup(tableFinder => tableFinder.getRangeContainingLine(It.isAny(), 0)) + .returns(() => null); + + const result = sut.hasTableAtCursor(document, 0); + + assert.strictEqual(result, false); + }); + + test('prettifyTableAtCursor() returns false when no table is found', async () => { + const sut = createSut(); + const textEditor = Mock.ofType(); + const document = new MarkdownTextDocumentStub('|A|B|\n|-|-|\n|1|2|'); + const selection = new vscode.Selection(0, 0, 0, 0); + + textEditor.setup(e => e.document).returns(() => document); + textEditor.setup(e => e.selection).returns(() => selection); + textEditor.setup(e => e.edit(It.isAny())).returns(() => Promise.resolve(true)); + + _tableFinder + .setup(tableFinder => tableFinder.getRangeContainingLine(It.isAny(), 0)) + .returns(() => null); + + const result = await sut.prettifyTableAtCursor(textEditor.object); + + assert.strictEqual(result, false); + textEditor.verify(e => e.edit(It.isAny()), Times.never()); + }); + + test('prettifyTableAtCursor() formats the table when one is found', async () => { + const sut = createSut(); + const textEditor = Mock.ofType(); + const document = new MarkdownTextDocumentStub('|A|B|\n|-|-|\n|1|2|'); + const selection = new vscode.Selection(1, 0, 1, 0); + + textEditor.setup(e => e.document).returns(() => document); + textEditor.setup(e => e.selection).returns(() => selection); + textEditor.setup(e => e.edit(It.isAny())).returns(() => Promise.resolve(true)); + + _tableFinder + .setup(tableFinder => tableFinder.getRangeContainingLine(It.isAny(), 1)) + .returns(() => new Range(0, 2)); + _singleTablePrettyfier + .setup(prettifier => prettifier.prettifyTable(It.isAny(), It.isAny())) + .returns(() => '|A|B|\n|-|-|\n|1|2|'); + + const result = await sut.prettifyTableAtCursor(textEditor.object); + + assert.strictEqual(result, true); + textEditor.verify(e => e.edit(It.isAny()), Times.once()); + }); + + function createSut(): TableAtCursorPrettyfier { + return new TableAtCursorPrettyfier(_tableFinder.object, _singleTablePrettyfier.object); + } +}); diff --git a/test/unitTests/tableFinding/tableFinder.test.ts b/test/unitTests/tableFinding/tableFinder.test.ts index 3a2bc2d..7316ffa 100644 --- a/test/unitTests/tableFinding/tableFinder.test.ts +++ b/test/unitTests/tableFinding/tableFinder.test.ts @@ -218,6 +218,107 @@ suite("TableFinder tests", () => { assert.deepStrictEqual(range, new Range(6, 9)); }); + test("getRangeContainingLine() returns the table for a cursor on the header row", () => { + const sut = createSut(); + const document = new Document(`|Primitive Type|Size(bit)|Wrapper + |-|-|- + |short|16|Short + |int|32|Integer`); + + let range = sut.getRangeContainingLine(document, 0); + + assert.deepStrictEqual(range, new Range(0, 3)); + }); + + test("getRangeContainingLine() returns the table for a cursor on a body row", () => { + const sut = createSut(); + const document = new Document(`|Primitive Type|Size(bit)|Wrapper + |-|-|- + |short|16|Short + |int|32|Integer`); + + let range = sut.getRangeContainingLine(document, 3); + + assert.deepStrictEqual(range, new Range(0, 3)); + }); + + test("getRangeContainingLine() returns the table containing a line inside the first table", () => { + const sut = createSut(); + const document = new Document(` + |Primitive Type|Size(bit)|Wrapper + |-|-|- + |short|16|Short + |int|32|Integer + + |Primitive Type|Size(bit)|Wrapper + |-|-|- + |short|16|Short + |int|32|Integer`); + + let range = sut.getRangeContainingLine(document, 3); + + assert.deepStrictEqual(range, new Range(1, 4)); + }); + + test("getRangeContainingLine() returns the table for a cursor on the separator row", () => { + const sut = createSut(); + const document = new Document(`|A|B|\n|-|-|\n|1|2|`); + + let range = sut.getRangeContainingLine(document, 1); + + assert.deepStrictEqual(range, new Range(0, 2)); + }); + + test("getRangeContainingLine() returns the second table when the cursor is in the second table", () => { + const sut = createSut(); + const document = new Document(`|A|B|\n|-|-|\n|1|2|\n\n|C|D|\n|-|-|\n|3|4|`); + + let range = sut.getRangeContainingLine(document, 6); + + assert.deepStrictEqual(range, new Range(4, 6)); + }); + + test("getRangeContainingLine() returns null for an out of range line", () => { + const sut = createSut(); + const document = new Document(`|A|B|\n|-|-|\n|1|2|`); + + let range = sut.getRangeContainingLine(document, 10); + + assert.strictEqual(range, null); + }); + + test("getRangeContainingLine() returns null for a line between tables", () => { + const sut = createSut(); + const document = new Document(` + |Primitive Type|Size(bit)|Wrapper + |-|-|- + |short|16|Short + |int|32|Integer + + |Primitive Type|Size(bit)|Wrapper + |-|-|- + |short|16|Short + |int|32|Integer`); + + let range = sut.getRangeContainingLine(document, 5); + + assert.strictEqual(range, null); + }); + + test("getRangeContainingLine() returns null when the cursor is inside an ignored block", () => { + const sut = createSut(); + const document = new Document(` + |Primitive Type|Size(bit)|Wrapper + |-|-|- + |short|16|Short + |int|32|Integer + `); + + let range = sut.getRangeContainingLine(document, 3); + + assert.strictEqual(range, null); + }); + function createSut() { return new TableFinder(new TableValidator(new SelectionInterpreter(true))); }