From e0437965e1907ea7ead726b6565e00ced22c91fc Mon Sep 17 00:00:00 2001 From: Robert Rollins Date: Fri, 2 May 2025 16:20:35 -0700 Subject: [PATCH] First draft of new closeHangingIndent option Based on manual testing, this change does what I want, except in one situation, and has one bug that I'm not sure how to fix. From this: ``` some_long_function_name("test") ``` You get this just by pressing Enter in the right places: ``` some_long_function_name( "test" ) ``` From this: ``` some_long_function_name({"logger": {"info": logger.info}}) ``` You can get this from just Enter as well: ``` some_long_function_name( { "logger": { "info": logger.info } } ) ``` However, from this: ``` some_long_function_name({ "WSGIErrorsWrapper": WSGIErrorsWrapper}) ``` Pressing Enter before the } doesn't put the }) at the start of the line. And pressing Enter before the ) mysteriously puts it right under the "e" in Wrapper, even if you've already pressed Enter before the }. Not sure what to do about that. Also, the complex hanging indent case mentioned in the comments in nextIndentationLevel no longer works as intended. ``` x = [ 0, 1, 2, [3, 4, 5, 6, 7, 8], ] ``` Becomes this after pressing Enter between 8 and ] : ``` x = [ 0, 1, 2, [3, 4, 5, 6, 7, 8 ], ] ``` I imagine this is undesired, but I don't know how to accomplish my goal without breaking this. The code path for my two successes and this failure are the same (the first if block at the end of nextIndentationLevel()). --- package.json | 5 +++++ src/indent.ts | 13 ++++++++++--- src/parser.ts | 16 ++++++++++++---- src/test/suite/indent.test.ts | 1 + 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d1af960..54cdf43 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,11 @@ "type": "boolean", "default": false, "description": "When creating a hanging indent, do not put the closing bracket on its own line." + }, + "pythonIndent.closeHangingIndent": { + "type": "boolean", + "default": false, + "description": "Close a hanging indent when pressing Enter right before the closing bracket." } } } diff --git a/src/indent.ts b/src/indent.ts index 89e4d28..5831b88 100644 --- a/src/indent.ts +++ b/src/indent.ts @@ -29,14 +29,20 @@ export function newlineAndIndent( let toInsert = '\n'; try { + // TODO: Is this if test necessary? The keybindings in package.json already check this. if (textEditor.document.languageId === 'python') { const lines = textEditor.document.getText( new vscode.Range(0, 0, position.line, position.character)).split("\n"); + const remainder = textEditor.document.getText( + new vscode.Range(position.line, position.character, position.line, currentLine.length)).trim(); + // Tell editsToMake to add a dedent if the curser is right before the closing bracket for a hanging indent. + const dedentIfHanging = "]})".includes(remainder[0]) && settings.closeHangingIndent; const edits = editsToMake( lines, currentLine, tabSize, position.line, position.character, settings.trimLinesWithOnlyWhitespace, - settings.keepHangingBracketOnLine); + settings.keepHangingBracketOnLine, + dedentIfHanging); toInsert = edits.insert; edits.deletes.forEach(range => { edit.delete(range); }); hanging = edits.hanging; @@ -64,9 +70,10 @@ export function editsToMake( lineNum: number, charNum: number, trimLinesWithOnlyWhitespace: boolean, - keepHangingBracketOnLine: boolean + keepHangingBracketOnLine: boolean, + dedentIfHanging?: boolean ): { insert: string; deletes: vscode.Range[]; hanging: Hanging } { - let { nextIndentationLevel: indent, parseOutput: parseOut } = indentationInfo(lines, tabSize); + let { nextIndentationLevel: indent, parseOutput: parseOut } = indentationInfo(lines, tabSize, dedentIfHanging); let deletes: vscode.Range[] = []; // If cursor has whitespace to the right, followed by non-whitespace, diff --git a/src/parser.ts b/src/parser.ts index 02c2227..7dababb 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -20,7 +20,7 @@ export function indentationLevel(line: string): number { return line.search(/\S|$/); } -function nextIndentationLevel(parseOutput: IParseOutput, lines: string[], tabSize: number): number { +function nextIndentationLevel(parseOutput: IParseOutput, lines: string[], tabSize: number, dedentIfHanging?: boolean): number { const row = lines.length - 1; // openBracketStack: A stack of [row, col] pairs describing where open brackets are // lastClosedRow: Either empty, or an array [rowOpen, rowClose] describing the rows @@ -85,7 +85,11 @@ function nextIndentationLevel(parseOutput: IParseOutput, lines: string[], tabSiz // Thus, nothing has happened that could have changed the // indentation level since the previous line, so // we should use whatever indent we are given. - return indentationLevel(lines[row]); + indentColumn = indentationLevel(lines[row]); + if (dedentIfHanging) { + // We're in a hanging indent, so we obey dedentIfHanging. + indentColumn -= tabSize; + } } else if (justClosedBracket && closedBracketOpenedAfterLineWithCurrentOpen) { // A bracket that was opened after the most recent open // bracket was closed on the line we just finished typing. @@ -108,6 +112,10 @@ function nextIndentationLevel(parseOutput: IParseOutput, lines: string[], tabSiz // before the "9", because it would try to match it up with the // open bracket instead of using the hanging indent. indentColumn = indentationLevel(lines[last_closed_info.open]); + if (dedentIfHanging) { + // We're in a hanging indent, so we obey dedentIfHanging. + indentColumn -= tabSize; + } } else { // lastOpenBracketLocation[1] is the column where the bracket was, // so need to bump up the indentation by one @@ -117,9 +125,9 @@ function nextIndentationLevel(parseOutput: IParseOutput, lines: string[], tabSiz return indentColumn; } -export function indentationInfo(lines: string[], tabSize: number): { nextIndentationLevel: number; parseOutput: IParseOutput } { +export function indentationInfo(lines: string[], tabSize: number, dedentIfHanging?: boolean): { nextIndentationLevel: number; parseOutput: IParseOutput } { const parseOutput = parse_lines(lines); - const nextIndent = nextIndentationLevel(parseOutput, lines, tabSize); + const nextIndent = nextIndentationLevel(parseOutput, lines, tabSize, dedentIfHanging); return { nextIndentationLevel: nextIndent, parseOutput }; } diff --git a/src/test/suite/indent.test.ts b/src/test/suite/indent.test.ts index cf95247..7235e09 100644 --- a/src/test/suite/indent.test.ts +++ b/src/test/suite/indent.test.ts @@ -382,6 +382,7 @@ def f(): ] ]; simpleCases.forEach((inputOutput, index) => { + // TODO: add tests for new closeHangingIndent option. Probbaly other tests need edits, too. let paramGrid: [boolean, boolean][] = [ [false, false], [false, true], [true, false], [true, true]]; test("simple case # " + (index + 1).toString(), async () => {