Fix search.selection for punctuation and non-ASCII words (fixes #179).

This commit is contained in:
Grégoire Geis 2021-06-23 22:43:10 +02:00
parent b1bf0b8550
commit 938fcb2873
5 changed files with 186 additions and 35 deletions

44
src/commands/README.md generated
View File

@ -76,9 +76,9 @@
<tr><td><a href="./search.ts#L26"><code>search.backward</code></a></td><td>Search backward</td><td><code>Alt+/</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L27"><code>search.backward.extend</code></a></td><td>Search backward (extend)</td><td><code>Shift+Alt+/</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L25"><code>search.extend</code></a></td><td>Search (extend)</td><td><code>Shift+/</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L138"><code>search.next.add</code></a></td><td>Add next match</td><td><code>Shift+N</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L139"><code>search.previous</code></a></td><td>Select previous match</td><td><code>Alt+N</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L140"><code>search.previous.add</code></a></td><td>Add previous match</td><td><code>Shift+Alt+N</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L162"><code>search.next.add</code></a></td><td>Add next match</td><td><code>Shift+N</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L163"><code>search.previous</code></a></td><td>Select previous match</td><td><code>Alt+N</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L164"><code>search.previous.add</code></a></td><td>Add previous match</td><td><code>Shift+Alt+N</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./search.ts#L94"><code>search.selection.smart</code></a></td><td>Search current selection (smart)</td><td><code>Shift+8</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="#searchselection"><code>search.selection</code></a></td><td>Search current selection</td><td><code>Shift+Alt+8</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td rowspan=35><a href="#seek"><code>seek</code></a></td><td><a href="#seekenclosing"><code>seek.enclosing</code></a></td><td>Select to next enclosing character</td><td><code>M</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
@ -172,17 +172,17 @@
<tr><td><a href="#selectionsselect"><code>selections.select</code></a></td><td>Select within selections</td><td><code>S</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L311"><code>selections.clear.main</code></a></td><td>Clear main selections</td><td><code>Alt+Space</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L310"><code>selections.clear.secondary</code></a></td><td>Clear secondary selections</td><td><code>Space</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L697"><code>selections.copy.above</code></a></td><td>Copy selections above</td><td><code>Shift+Alt+C</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L661"><code>selections.faceBackward</code></a></td><td>Backward selections</td><td></td></tr>
<tr><td><a href="./selections.ts#L660"><code>selections.faceForward</code></a></td><td>Forward selections</td><td><code>Shift+Alt+;</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L693"><code>selections.copy.above</code></a></td><td>Copy selections above</td><td><code>Shift+Alt+C</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L657"><code>selections.faceBackward</code></a></td><td>Backward selections</td><td></td></tr>
<tr><td><a href="./selections.ts#L656"><code>selections.faceForward</code></a></td><td>Forward selections</td><td><code>Shift+Alt+;</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L308"><code>selections.filter.regexp</code></a></td><td>Keep matching selections</td><td><code>Alt+K</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L309"><code>selections.filter.regexp.inverse</code></a></td><td>Clear matching selections</td><td><code>Shift+Alt+K</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L777"><code>selections.hideIndices</code></a></td><td>Hide selection indices</td><td></td></tr>
<tr><td><a href="./selections.ts#L773"><code>selections.hideIndices</code></a></td><td>Hide selection indices</td><td></td></tr>
<tr><td><a href="./selections.ts#L246"><code>selections.pipe.append</code></a></td><td>Pipe and append</td><td><code>Shift+1</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L247"><code>selections.pipe.prepend</code></a></td><td>Pipe and prepend</td><td><code>Shift+Alt+1</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L245"><code>selections.pipe.replace</code></a></td><td>Pipe and replace</td><td><code>Shift+\</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L579"><code>selections.reduce.edges</code></a></td><td>Reduce selections to their ends</td><td><code>Shift+Alt+S</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L776"><code>selections.showIndices</code></a></td><td>Show selection indices</td><td></td></tr>
<tr><td><a href="./selections.ts#L575"><code>selections.reduce.edges</code></a></td><td>Reduce selections to their ends</td><td><code>Shift+Alt+S</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="./selections.ts#L772"><code>selections.showIndices</code></a></td><td>Show selection indices</td><td></td></tr>
<tr><td><a href="#selectionssplit"><code>selections.split</code></a></td><td>Split selections</td><td><code>Shift+S</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="#selectionssplitLines"><code>selections.splitLines</code></a></td><td>Split selections at line boundaries</td><td><code>Alt+S</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
<tr><td><a href="#selectionstoggleIndices"><code>selections.toggleIndices</code></a></td><td>Toggle selection indices</td><td><code>Enter</code> (<code>editorTextFocus && dance.mode == 'normal'</code>)</td></tr>
@ -757,7 +757,7 @@ This command:
- accepts a register (by default, it uses `slash`).
- takes an argument `smart` of type `boolean`.
### [`search.next`](./search.ts#L131-L150)
### [`search.next`](./search.ts#L155-L174)
Select next match.
@ -1151,7 +1151,7 @@ This command:
- takes an argument `inverse` of type `boolean`.
- takes an input of type `Input<string>`.
### [`selections.select`](./selections.ts#L352-L363)
### [`selections.select`](./selections.ts#L350-L361)
Select within selections.
@ -1160,7 +1160,7 @@ This command:
- takes an argument `interactive` of type `boolean`.
- takes an input of type `Input<string | RegExp>`.
### [`selections.split`](./selections.ts#L384-L396)
### [`selections.split`](./selections.ts#L380-L392)
Split selections.
@ -1170,7 +1170,7 @@ This command:
- takes an argument `interactive` of type `boolean`.
- takes an input of type `Input<string | RegExp>`.
### [`selections.splitLines`](./selections.ts#L421-L432)
### [`selections.splitLines`](./selections.ts#L417-L428)
Split selections at line boundaries.
@ -1179,25 +1179,25 @@ This command:
- may be repeated with a given number of repetitions.
- takes an argument `excludeEol` of type `boolean`.
### [`selections.expandToLines`](./selections.ts#L475-L482)
### [`selections.expandToLines`](./selections.ts#L471-L478)
Expand to lines.
Expand selections to contain full lines (including end-of-line characters).
### [`selections.trimLines`](./selections.ts#L509-L516)
### [`selections.trimLines`](./selections.ts#L505-L512)
Trim lines.
Trim selections to only contain full lines (from start to line break).
### [`selections.trimWhitespace`](./selections.ts#L541-L548)
### [`selections.trimWhitespace`](./selections.ts#L537-L544)
Trim whitespace.
Trim whitespace at beginning and end of selections.
### [`selections.reduce`](./selections.ts#L567-L586)
### [`selections.reduce`](./selections.ts#L563-L582)
Reduce selections to their cursor.
@ -1213,7 +1213,7 @@ This command:
- takes an argument `empty` of type `boolean`.
- takes an argument `where` of type `"active" | "anchor" | "start" | "end" | "both"`.
### [`selections.changeDirection`](./selections.ts#L648-L663)
### [`selections.changeDirection`](./selections.ts#L644-L659)
Change direction of selections.
@ -1226,7 +1226,7 @@ Change direction of selections.
| Forward selections | `faceForward` | `a-:` (normal) | `[".selections.changeDirection", { direction: 1 }]` |
| Backward selections | `faceBackward` | | `[".selections.changeDirection", { direction: -1 }]` |
### [`selections.copy`](./selections.ts#L688-L706)
### [`selections.copy`](./selections.ts#L684-L702)
Copy selections below.
@ -1240,15 +1240,15 @@ Copy selections below.
This command:
- may be repeated with a given number of repetitions.
### [`selections.merge`](./selections.ts#L740-L745)
### [`selections.merge`](./selections.ts#L736-L741)
Merge contiguous selections.
### [`selections.open`](./selections.ts#L749-L752)
### [`selections.open`](./selections.ts#L745-L748)
Open selected file.
### [`selections.toggleIndices`](./selections.ts#L767-L784)
### [`selections.toggleIndices`](./selections.ts#L763-L780)
Toggle selection indices.

View File

@ -106,25 +106,49 @@ export function selection(
for (const selection of selections) {
let text = escapeForRegExp(document.getText(selection));
if (smart) {
const firstLine = document.lineAt(selection.start).text,
firstLineStart = selection.start.character;
if (text.length === 0) {
continue;
}
if (firstLineStart === 0 || !isWord!(firstLine.charCodeAt(firstLineStart - 1))) {
text = `\\b${text}`;
if (smart) {
let firstLine: string | undefined,
isBeginningOfWord = isWord!(text.charCodeAt(0));
const firstLineStart = selection.start.character;
if (isBeginningOfWord && firstLineStart > 0) {
firstLine = document.lineAt(selection.start).text;
isBeginningOfWord = !isWord!(firstLine.charCodeAt(firstLineStart - 1));
}
const lastLine = selection.isSingleLine ? firstLine : document.lineAt(selection.end).text,
lastLineEnd = selection.end.character;
const lastLineEnd = selection.end.character,
lastLine = selection.isSingleLine && firstLine !== undefined
? firstLine
: document.lineAt(selection.end).text,
isEndOfWord = lastLineEnd + 1 < lastLine.length
&& isWord!(lastLine.charCodeAt(lastLineEnd - 1))
&& !isWord!(lastLine.charCodeAt(lastLineEnd));
if (lastLineEnd >= lastLine.length || !isWord!(lastLine.charCodeAt(lastLineEnd))) {
text = `${text}\\b`;
if (isBeginningOfWord) {
const prefix = text.charCodeAt(0) < 0x80 ? "\\b" : "(?<=^|\\P{L})";
text = prefix + text;
}
if (isEndOfWord) {
const suffix = text.charCodeAt(text.length - 1) < 0x80 ? "\\b" : "(?=\\P{L}|$)";
text += suffix;
}
}
texts.push(text);
}
if (texts.length === 0) {
throw new Error("all selections are empty");
}
register.set(texts);
}

View File

@ -347,8 +347,6 @@ export function filter(
});
}
let lastSelectInput: RegExp | undefined;
/**
* Select within selections.
*
@ -372,8 +370,6 @@ export function select(
input = new RegExp(input, "mu");
}
lastSelectInput = input;
Selections.set(Selections.bottomToTop(Selections.selectWithin(input, selections)));
return Promise.resolve(input);

View File

@ -0,0 +1,53 @@
# ascii
```
a a
^ 0
```
## ascii search-selected-smart
[up](#ascii)
- .search.selection.smart
- .search.next
```
a a
^ 0
```
# ascii-punct
```
. .
^ 0
```
## ascii-punct search-selected-smart
[up](#ascii-punct)
- .search.selection.smart
- .search.next
```
. .
^ 0
```
# unicode
```
é é
^ 0
```
## unicode search-selected-smart
[up](#unicode)
- .search.selection.smart
- .search.next
```
é é
^ 0
```

View File

@ -0,0 +1,78 @@
import * as vscode from "vscode";
import { executeCommand, ExpectedDocument, groupTestsByParentName } from "../utils";
suite("./test/suite/commands/search-selected.md", function () {
// Set up document.
let document: vscode.TextDocument,
editor: vscode.TextEditor;
this.beforeAll(async () => {
document = await vscode.workspace.openTextDocument();
editor = await vscode.window.showTextDocument(document);
editor.options.insertSpaces = true;
editor.options.tabSize = 2;
await executeCommand("dance.dev.setSelectionBehavior", { mode: "normal", value: "caret" });
});
this.afterAll(async () => {
await executeCommand("workbench.action.closeActiveEditor");
});
test("ascii > search-selected-smart", async function () {
// Set-up document to be in expected initial state.
await ExpectedDocument.apply(editor, 6, String.raw`
a a
^ 0
`);
// Perform all operations.
await executeCommand("dance.search.selection.smart");
await executeCommand("dance.search.next");
// Ensure document is as expected.
ExpectedDocument.assertEquals(editor, "./test/suite/commands/search-selected.md:8:1", 6, String.raw`
a a
^ 0
`);
});
test("ascii-punct > search-selected-smart", async function () {
// Set-up document to be in expected initial state.
await ExpectedDocument.apply(editor, 6, String.raw`
. .
^ 0
`);
// Perform all operations.
await executeCommand("dance.search.selection.smart");
await executeCommand("dance.search.next");
// Ensure document is as expected.
ExpectedDocument.assertEquals(editor, "./test/suite/commands/search-selected.md:26:1", 6, String.raw`
. .
^ 0
`);
});
test("unicode > search-selected-smart", async function () {
// Set-up document to be in expected initial state.
await ExpectedDocument.apply(editor, 6, String.raw`
é é
^ 0
`);
// Perform all operations.
await executeCommand("dance.search.selection.smart");
await executeCommand("dance.search.next");
// Ensure document is as expected.
ExpectedDocument.assertEquals(editor, "./test/suite/commands/search-selected.md:44:1", 6, String.raw`
é é
^ 0
`);
});
groupTestsByParentName(this);
});