Skip to content

Commit

Permalink
Improved IND vs RAW interaction: use bracket scope encompassing block…
Browse files Browse the repository at this point in the history
… endpoint.
  • Loading branch information
lukstafi committed Jan 31, 2023
1 parent fbfca27 commit 2511182
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 32 deletions.
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,7 @@ I ignore defined-symbols that are out-of-order with respect to the syntactic str

If Navi Parens logs assertion failure, maybe the language has delimiters other than those in the configuration.

When navigating down out of a scope with both indentation and bracket scopes enabled, where the scope brackets are both the first non-white characters on their lines (as often happens with braces in JSON files), the behavior can be a bit unintuitive: the cursor can end up before the closing bracket/brace. That is because we jump out of the indentation scope, since it is contained (not just overlapping) in the brackets scope. We remain within the brackets scope. It is the intended behavior.

![Indentation block scope vs brackets scope](animations/indentation-vs-brackets-outer.gif)
On the other hand, when the overlap is without inclusion, we prefer the bracket scope for navigating out of a scope.
When navigating down out of a scope with both indentation and bracket scopes enabled, where the scope brackets are both the first non-white characters on their lines (as often happens with braces in JSON files), the behavior can be a bit unintuitive: the cursor can end up before the closing bracket/brace. That is because we jump out of the indentation scope, since it is contained (not just overlapping) in the brackets scope. We remain within the brackets scope. It is the intended behavior. On the other hand, when the overlap is without inclusion, we prefer the bracket scope for navigating out of a scope.

Some Navi Parens commands will misbehave if they are executed before a document editor is fully initialized. Specifically, the `Semantic` and `JumpToBrackets` modes require the corresponding initializations, while the `Indentation` and `Raw` modes are good-to-go right away since they only look at the text of a document.

Expand Down
Binary file removed animations/indentation-vs-brackets-outer.gif
Binary file not shown.
19 changes: 19 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,15 @@ function findOuterBracketRaw(
return null;
}

async function findBracketScopeOverPos(
textEditor: vscode.TextEditor, before: boolean, pos: vscode.Position): Promise<vscode.Selection | null> {
const configuration = vscode.workspace.getConfiguration();
const bracketsMode = configuration.get<string>("navi-parens.bracketScopeMode");
let bracketScope = bracketsMode === "JumpToBracket" ? await findOuterBracket(textEditor, before, pos) :
bracketsMode === "Raw" ? findOuterBracketRaw(textEditor, before, pos) : null;
return bracketScope;
}

function findOuterIndentation(
textEditor: vscode.TextEditor, before: boolean, near: boolean, pos: vscode.Position): vscode.Selection | null {
const doc = textEditor.document;
Expand Down Expand Up @@ -416,6 +425,11 @@ export async function goToOuterScope(textEditor: vscode.TextEditor, select: bool
}
return;
}
const maybeBracketOverride = await findBracketScopeOverPos(textEditor, before, result);
if (maybeBracketOverride && (!blockScope || !maybeBracketOverride.contains(blockScope)) &&
(!bracketScope || !maybeBracketOverride.contains(bracketScope))) {
result = maybeBracketOverride.active;
}
const anchor = select ? savedSelection.anchor : result;
textEditor.selection = new vscode.Selection(anchor, result);
if (!state.lastVisibleRange.contains(textEditor.selection)) {
Expand Down Expand Up @@ -629,6 +643,11 @@ export async function goPastSiblingScope(textEditor: vscode.TextEditor, select:
}
return;
}
const maybeBracketOverride = await findBracketScopeOverPos(textEditor, before, targetPos);
if (maybeBracketOverride && (!blockScope || !maybeBracketOverride.contains(blockScope)) &&
(!bracketScope || !maybeBracketOverride.contains(bracketScope))) {
targetPos = maybeBracketOverride.active;
}
const anchor = select ? savedSelection.anchor : targetPos;
textEditor.selection = new vscode.Selection(anchor, targetPos);
// jumpToBracket could have moved the screen.
Expand Down
46 changes: 18 additions & 28 deletions src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,6 @@ suite('Extension Test Suite', () => {
'goPastPreviousScope', mode, 'typescript'
));
test('Basic syntax navigation: begin scope with IND ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which here comes from indentation.
// (For indentation the less-indented line is a big delimiter, its start is outside but it is not inside.)
`
for (let index = 0; index < array.length; index++) {
Expand All @@ -248,7 +247,6 @@ suite('Extension Test Suite', () => {
'goToBeginScope', mode, 'typescript'
));
test('Basic syntax navigation: end scope with IND ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
for (let index = 0; index < array.length; index++) {
const element = @array[index];^
Expand All @@ -258,51 +256,45 @@ suite('Extension Test Suite', () => {
));

test('Tricky syntax navigation: next scope with IND from code line ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
@for (let index = 0; index < array.length; index++)^ {
const element = array[index];
}
`,
'goPastNextScope', mode, 'typescript'
));
// FIXME(12): make this work by default, make it a config if this "offends the logic of scopes".
test('Tricky syntax navigation: next scope with IND from code line 2 ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
@{
let local = true;
}
^{
}^
{
let local = 'true';
}
`,
'goPastNextScope', mode, 'typescript', true
'goPastNextScope', mode, 'typescript'
));
test('Tricky syntax navigation: next scope with IND from code line 3 ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
@{
let local = true; }
^{
let local = true; }^
{
let local = 'true';
}
`,
'goPastNextScope', mode, 'typescript', true
'goPastNextScope', mode, 'typescript'
));
test('Tricky syntax navigation: next scope with IND from empty line ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
@
for (let index = 0; index < array.length; index++) {
const element = array[index];
}^
`,
'goPastNextScope', mode, 'typescript', true
'goPastNextScope', mode, 'typescript'
));

test('Tricky syntax navigation: next scope with IND from empty line to empty line ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
@
def foo(bar, baz):
Expand All @@ -314,7 +306,6 @@ suite('Extension Test Suite', () => {
'goPastNextScope', mode, 'python'
));
test('Tricky syntax navigation: next scope with IND from code line 2 ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
@def foo(bar, baz)^:
bar()
Expand All @@ -325,7 +316,6 @@ suite('Extension Test Suite', () => {
'goPastNextScope', mode, 'python'
));
test('Tricky syntax navigation: down scope with IND from header line no change ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
@^def foo(bar, baz):
bar()
Expand All @@ -336,7 +326,6 @@ suite('Extension Test Suite', () => {
'goToDownScope', mode, 'python'
));
test('Tricky syntax navigation: down scope with IND from header line 2 no change ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
d@^ef foo(bar, baz):
bar()
Expand All @@ -347,7 +336,6 @@ suite('Extension Test Suite', () => {
'goToDownScope', mode, 'python'
));
test('Tricky syntax navigation: down scope with IND from inner line ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
def foo(bar, baz):
ba@r()
Expand All @@ -358,7 +346,6 @@ suite('Extension Test Suite', () => {
'goToDownScope', mode, 'python'
));
test('Tricky syntax navigation: previous scope with IND from code line ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
^def foo(bar, baz):
Expand All @@ -370,7 +357,6 @@ suite('Extension Test Suite', () => {
'goPastPreviousScope', mode, 'python'
));
test('Tricky syntax navigation: previous scope with IND from attached code line ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
^def foo(bar, baz):
Expand All @@ -381,7 +367,6 @@ suite('Extension Test Suite', () => {
'goPastPreviousScope', mode, 'python'
));
test('Tricky syntax navigation: previous scope with IND from empty line ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
^def foo(bar, baz):
bar()
Expand All @@ -392,7 +377,6 @@ suite('Extension Test Suite', () => {
'goPastPreviousScope', mode, 'python'
));
test('Tricky syntax navigation: previous scope with IND from hanging empty line ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
def foo(bar, baz):
Expand All @@ -403,7 +387,6 @@ suite('Extension Test Suite', () => {
'goPastPreviousScope', mode, 'python'
));
test('Tricky syntax navigation: previous scope with IND from separating empty line ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
def foo(bar, baz):
Expand All @@ -415,7 +398,6 @@ suite('Extension Test Suite', () => {
'goPastPreviousScope', mode, 'python'
));
test('Tricky syntax navigation: previous scope with IND to separating empty line ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
pass
Expand All @@ -428,7 +410,6 @@ suite('Extension Test Suite', () => {
'goPastPreviousScope', mode, 'python'
));
test('Tricky syntax navigation: previous scope with IND from and to separating empty line ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
pass
Expand All @@ -441,16 +422,25 @@ suite('Extension Test Suite', () => {
'goPastPreviousScope', mode, 'python'
));

test('Tricky syntax navigation: down scope with IND from indented line ' + mode, testCase(
`
for (let index = 0;
index < array.length;
index++)@ {
const element = array[index];
}^
`,
'goToDownScope', mode, 'typescript'
));
test('Tricky syntax navigation: next scope with IND from indented line ' + mode, testCase(
// For begin/end scope, we always pick the nearer end-point, which comes from indentation.
`
for (let index = 0;
index < array.length;
index++)@ {
const element = array[index];
}^
`,
'goPastNextScope', mode, 'typescript' , true
'goPastNextScope', mode, 'typescript', true
));

}
Expand Down

0 comments on commit 2511182

Please sign in to comment.