mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-01 12:42:59 -05:00
When folding markdown headers, leave single new line before next header
This commit is contained in:
@@ -31,6 +31,9 @@ export default class MarkdownFoldingProvider implements vscode.FoldingProvider {
|
||||
for (let i = startIndex + 1; i < toc.length; ++i) {
|
||||
if (toc[i].level <= entry.level) {
|
||||
end = toc[i].line - 1;
|
||||
if (document.lineAt(end).isEmptyOrWhitespace && end >= start + 1) {
|
||||
end = end - 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
99
extensions/markdown/src/test/foldingProvider.test.ts
Normal file
99
extensions/markdown/src/test/foldingProvider.test.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { MarkdownContributions } from '../markdownExtensions';
|
||||
import MarkdownFoldingProvider from '../features/foldingProvider';
|
||||
import { InMemoryDocument } from './inMemoryDocument';
|
||||
|
||||
const testFileName = vscode.Uri.parse('test.md');
|
||||
|
||||
suite('markdown.FoldingProvider', () => {
|
||||
test('Should not return anything for empty document', async () => {
|
||||
const folds = await getFoldsForDocument(``);
|
||||
assert.strictEqual(folds.ranges.length, 0);
|
||||
});
|
||||
|
||||
test('Should not return anything for document without headers', async () => {
|
||||
const folds = await getFoldsForDocument(`a
|
||||
**b** afas
|
||||
a#b
|
||||
a`);
|
||||
assert.strictEqual(folds.ranges.length, 0);
|
||||
});
|
||||
|
||||
test('Should fold from header to end of document', async () => {
|
||||
const folds = await getFoldsForDocument(`a
|
||||
# b
|
||||
c
|
||||
d`);
|
||||
assert.strictEqual(folds.ranges.length, 1);
|
||||
const firstFold = folds.ranges[0];
|
||||
assert.strictEqual(firstFold.startLine, 1);
|
||||
assert.strictEqual(firstFold.endLine, 3);
|
||||
});
|
||||
|
||||
test('Should leave single newline before next header', async () => {
|
||||
const folds = await getFoldsForDocument(`
|
||||
# a
|
||||
x
|
||||
|
||||
# b
|
||||
y`);
|
||||
assert.strictEqual(folds.ranges.length, 2);
|
||||
const firstFold = folds.ranges[0];
|
||||
assert.strictEqual(firstFold.startLine, 1);
|
||||
assert.strictEqual(firstFold.endLine, 3);
|
||||
});
|
||||
|
||||
test('Should collapse multuple newlines to single newline before next header', async () => {
|
||||
const folds = await getFoldsForDocument(`
|
||||
# a
|
||||
x
|
||||
|
||||
|
||||
|
||||
# b
|
||||
y`);
|
||||
assert.strictEqual(folds.ranges.length, 2);
|
||||
const firstFold = folds.ranges[0];
|
||||
assert.strictEqual(firstFold.startLine, 1);
|
||||
assert.strictEqual(firstFold.endLine, 5);
|
||||
});
|
||||
|
||||
test('Should not collapse if there is no newline before next header', async () => {
|
||||
const folds = await getFoldsForDocument(`
|
||||
# a
|
||||
x
|
||||
# b
|
||||
y`);
|
||||
assert.strictEqual(folds.ranges.length, 2);
|
||||
const firstFold = folds.ranges[0];
|
||||
assert.strictEqual(firstFold.startLine, 1);
|
||||
assert.strictEqual(firstFold.endLine, 2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
async function getFoldsForDocument(contents: string) {
|
||||
const doc = new InMemoryDocument(testFileName, contents);
|
||||
const provider = new MarkdownFoldingProvider(newEngine());
|
||||
return await provider.provideFoldingRanges(doc, {}, new vscode.CancellationTokenSource().token);
|
||||
}
|
||||
|
||||
function newEngine(): MarkdownEngine {
|
||||
return new MarkdownEngine(new class implements MarkdownContributions {
|
||||
readonly previewScripts: vscode.Uri[] = [];
|
||||
readonly previewStyles: vscode.Uri[] = [];
|
||||
readonly previewResourceRoots: vscode.Uri[] = [];
|
||||
readonly markdownItPlugins: Promise<(md: any) => any>[] = [];
|
||||
});
|
||||
}
|
||||
|
||||
61
extensions/markdown/src/test/inMemoryDocument.ts
Normal file
61
extensions/markdown/src/test/inMemoryDocument.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class InMemoryDocument implements vscode.TextDocument {
|
||||
private readonly _lines: string[];
|
||||
|
||||
constructor(
|
||||
public readonly uri: vscode.Uri,
|
||||
private readonly _contents: string
|
||||
) {
|
||||
this._lines = this._contents.split(/\n/g);
|
||||
}
|
||||
|
||||
fileName: string = '';
|
||||
isUntitled: boolean = false;
|
||||
languageId: string = '';
|
||||
version: number = 1;
|
||||
isDirty: boolean = false;
|
||||
isClosed: boolean = false;
|
||||
eol: vscode.EndOfLine = vscode.EndOfLine.LF;
|
||||
|
||||
get lineCount(): number {
|
||||
return this._lines.length;
|
||||
}
|
||||
|
||||
lineAt(line: any): vscode.TextLine {
|
||||
return {
|
||||
lineNumber: line,
|
||||
text: this._lines[line],
|
||||
range: new vscode.Range(0, 0, 0, 0),
|
||||
firstNonWhitespaceCharacterIndex: 0,
|
||||
rangeIncludingLineBreak: new vscode.Range(0, 0, 0, 0),
|
||||
isEmptyOrWhitespace: false
|
||||
};
|
||||
}
|
||||
offsetAt(_position: vscode.Position): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
positionAt(_offset: number): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getText(_range?: vscode.Range | undefined): string {
|
||||
return this._contents;
|
||||
}
|
||||
getWordRangeAtPosition(_position: vscode.Position, _regex?: RegExp | undefined): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
validateRange(_range: vscode.Range): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
validatePosition(_position: vscode.Position): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
save(): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import 'mocha';
|
||||
import { TableOfContentsProvider } from '../tableOfContentsProvider';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { MarkdownContributions } from '../markdownExtensions';
|
||||
import { InMemoryDocument } from './inMemoryDocument';
|
||||
|
||||
const testFileName = vscode.Uri.parse('test.md');
|
||||
|
||||
@@ -83,62 +84,6 @@ suite('markdown.TableOfContentsProvider', () => {
|
||||
});
|
||||
});
|
||||
|
||||
class InMemoryDocument implements vscode.TextDocument {
|
||||
private readonly _lines: string[];
|
||||
|
||||
constructor(
|
||||
public readonly uri: vscode.Uri,
|
||||
private readonly _contents: string
|
||||
) {
|
||||
this._lines = this._contents.split(/\n/g);
|
||||
}
|
||||
|
||||
fileName: string = '';
|
||||
isUntitled: boolean = false;
|
||||
languageId: string = '';
|
||||
version: number = 1;
|
||||
isDirty: boolean = false;
|
||||
isClosed: boolean = false;
|
||||
eol: vscode.EndOfLine = vscode.EndOfLine.LF;
|
||||
|
||||
get lineCount(): number {
|
||||
return this._lines.length;
|
||||
}
|
||||
|
||||
lineAt(line: any): vscode.TextLine {
|
||||
return {
|
||||
lineNumber: line,
|
||||
text: this._lines[line],
|
||||
range: new vscode.Range(0, 0, 0, 0),
|
||||
firstNonWhitespaceCharacterIndex: 0,
|
||||
rangeIncludingLineBreak: new vscode.Range(0, 0, 0, 0),
|
||||
isEmptyOrWhitespace: false
|
||||
};
|
||||
}
|
||||
offsetAt(_position: vscode.Position): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
positionAt(_offset: number): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getText(_range?: vscode.Range | undefined): string {
|
||||
return this._contents;
|
||||
}
|
||||
getWordRangeAtPosition(_position: vscode.Position, _regex?: RegExp | undefined): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
validateRange(_range: vscode.Range): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
validatePosition(_position: vscode.Position): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
save(): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function newEngine(): MarkdownEngine {
|
||||
return new MarkdownEngine(new class implements MarkdownContributions {
|
||||
readonly previewScripts: vscode.Uri[] = [];
|
||||
|
||||
Reference in New Issue
Block a user