Skip to content

Commit

Permalink
Fixes #4: allows mixed tabs-spaces for indentation. (Heisenbug proble…
Browse files Browse the repository at this point in the history
…ms with 2 tests.)
  • Loading branch information
lukstafi committed Feb 14, 2023
1 parent e6bd784 commit e9427b6
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 10 deletions.
33 changes: 23 additions & 10 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,16 @@ async function findBracketScopeOverPos(
return bracketScope;
}

function countVisibleIndentation(textEditor: vscode.TextEditor, line: vscode.TextLine): number {
const tabSize = Number(textEditor.options.tabSize);
const updatedText = line.text.replace(/\t/g, " ".repeat(tabSize));
const firstNonWhite = Number(updatedText.search(/[^ ]/g));
if (firstNonWhite < 0) {
return updatedText.length / tabSize;
}
return firstNonWhite / tabSize;
}

function findOuterIndentation(
textEditor: vscode.TextEditor, before: boolean, near: boolean, pos: vscode.Position): vscode.Selection | null {
const doc = textEditor.document;
Expand All @@ -337,33 +347,35 @@ function findOuterIndentation(
let entryIndent = -1;
let previousNo = -1;
let previousIndent = -1;
let previousFirstNonWhitespace = -1;
let passingEmptyLine = false;
for (let lineNo = pos.line; 0 <= lineNo && lineNo < doc.lineCount; lineNo += direction[side]) {
const line = doc.lineAt(lineNo);
if (line.isEmptyOrWhitespace && lineNo < doc.lineCount - 1 && lineNo > 0) {
passingEmptyLine = true;
continue;
}
// TODO(4): handle tabs/spaces?
const indentation = line.firstNonWhitespaceCharacterIndex;
// Note: indentation is different than `firstNonWhitespaceCharacterIndex`.
const indentation = countVisibleIndentation(textEditor, line);
if (entryIndent < 0) { entryIndent = indentation; }
else if (indentation < entryIndent) {
if (near) {
if ((before && side === 0) || (!before && side === 1)) {
selection[side] = new vscode.Position(previousNo, previousIndent);
selection[side] = new vscode.Position(previousNo, previousFirstNonWhitespace);
} else {
// Return end of the previous line.
selection[side] = doc.lineAt(previousNo).range.end;
}
} else {
const previousLinePos = doc.lineAt(lineNo - direction[side]).range.end;
selection[side] = direction[side] === 1 && passingEmptyLine ?
previousLinePos : new vscode.Position(lineNo, indentation);
previousLinePos : new vscode.Position(lineNo, line.firstNonWhitespaceCharacterIndex);
}
break;
}
previousNo = lineNo;
previousIndent = indentation;
previousFirstNonWhitespace = line.firstNonWhitespaceCharacterIndex;
passingEmptyLine = false;
}
}
Expand Down Expand Up @@ -403,7 +415,7 @@ export async function goToOuterScope(textEditor: vscode.TextEditor, select: bool
new vscode.Selection(symbol.selectionRange.end, rangeEnd);
}
} else if (blockMode === "Indentation") {
blockScope = findOuterIndentation(textEditor, before, near, pos);
blockScope = await findOuterIndentation(textEditor, before, near, pos);
} else { console.assert(blockMode === "None", `Unknown Block Scope Mode ${blockMode}.`); }
// If one scope includes the other, pick the nearer target, otherwise pick the farther target.
let result = null;
Expand Down Expand Up @@ -543,15 +555,16 @@ function findSiblingIndentation(
passingEmptyLine = true;
continue;
}
// TODO(4): handle tabs?
const indentation = line.firstNonWhitespaceCharacterIndex;
// Note: indentation is different than `firstNonWhitespaceCharacterIndex`.
const indentation = countVisibleIndentation(textEditor, line);
if (noIndent < 0) {
noIndent = indentation;
}
else if (updated && indentation === noIndent) {
const entryPos = before ? doc.lineAt(entryNo).range.end : new vscode.Position(entryNo, noIndent);
const previousLinePos = doc.lineAt(lineNo - direction).range.end;
const leavePos = passingEmptyLine ? previousLinePos : new vscode.Position(lineNo, indentation);
const leavePos = passingEmptyLine ? previousLinePos :
new vscode.Position(lineNo, line.firstNonWhitespaceCharacterIndex);
return new vscode.Selection(entryPos, leavePos);
} else if (!updated && indentation > noIndent) {
updated = true;
Expand Down Expand Up @@ -599,8 +612,8 @@ export async function goPastSiblingScope(textEditor: vscode.TextEditor, select:
new vscode.Selection(candidate.start, candidate.end);
}
} else if (blockMode === "Indentation") {
blockScope = findSiblingIndentation(textEditor, before, pos);
scopeLimit = findOuterIndentation(textEditor, before, false, pos);
blockScope = await findSiblingIndentation(textEditor, before, pos);
scopeLimit = await findOuterIndentation(textEditor, before, false, pos);
} else { console.assert(blockMode === "None", `Unknown Block Scope Mode ${blockMode}.`); }

const bracketsMode = vscode.workspace.getConfiguration().get<string>("navi-parens.bracketScopeMode");
Expand Down
109 changes: 109 additions & 0 deletions src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,115 @@ suite('Extension Test Suite', () => {
'goPastPreviousScope', mode, 'python'
));
}
{
const mode = 'IND/NON';
test('Tab-space syntax navigation: up scope using IND ' + mode, testCase(
// The less-indented non-whitespace-starting-part line is like a big start delimiter for the scope.
`
for item in range:
^if condition:
pa@ss
elif condition:
pass
`,
'goToUpScope', mode, 'python'
));
test('Tab-space syntax navigation: down scope using IND ' + mode, testCase(
`
for item in range:
if condition:
pa@ss
^elif condition:
pass
`,
'goToDownScope', mode, 'python'
));
test('Tab-space syntax navigation: begin scope using IND ' + mode, testCase(
`
for item in range:
if condition:
^pa@ss
elif condition:
pass
`,
'goToBeginScope', mode, 'python'
));
test('Tab-space syntax navigation: begin scope using IND 2 ' + mode, testCase(
`
for item in range:
^if condition:
pass
@elif condition:
pass
`,
'goToBeginScope', mode, 'python'
));
test('Tab-space syntax navigation: begin scope using IND 3 ' + mode, testCase(
`
for item in range:
if condition:
^pass
pa@ss
elif condition:
pass
`,
'goToBeginScope', mode, 'python'
));
test('Tab-space syntax navigation: end scope using IND ' + mode, testCase(
`
for item in range:
if condition:
pa@ss^
elif condition:
pass
`,
'goToEndScope', mode, 'python'
));
test('Tab-space syntax navigation: end scope using IND 2 ' + mode, testCase(
`
for item in range:
if condition:
pa@ss
pass^
elif condition:
pass
`,
'goToEndScope', mode, 'python', true
));
test('Tab-space syntax navigation: next scope using IND ' + mode, testCase(
`
for item in range:
pa@ss
if condition:
pass
^elif condition:
pass
`,
'goPastNextScope', mode, 'python'
));

test('Tab-space syntax navigation: next scope using IND no-change ' + mode, testCase(
`
for item in range:
if condition:
pass
@^elif condition:
pass
`,
'goPastNextScope', mode, 'python'
));

test('Tab-space syntax navigation: previous scope using IND ' + mode, testCase(
`
for item in range:
^if condition:
pass
elif@ condition:
pass
`,
'goPastPreviousScope', mode, 'python', true
));
}
for (const mode of ['NON/RAW', 'NON/JTB']) {
test('Basic syntax navigation: begin scope without block scopes ' + mode, testCase(
`
Expand Down

0 comments on commit e9427b6

Please sign in to comment.