import * as vscode from 'vscode' ; import { Hanging , indentationInfo , indentationLevel , shouldHang } from 'python-indent-parser' ; export function newlineAndIndent ( textEditor , edit , args ) { if ( ! textEditor . selection . isEmpty ) { edit . delete ( textEditor . selection ) ; textEditor . selection = new vscode . Selection ( textEditor . selection . start , textEditor . selection . start ) ; } const position = textEditor . selection . active ; const tabSize = < number > textEditor . options . tabSize ! ; const insertionPoint = new vscode . Position ( position . line , position . character ) ; const currentLine = textEditor . document . lineAt ( position ) . text ; let snippetCursor = '$0' ; let settings = vscode . workspace . getConfiguration ( 'pythonIndent' ) ; if ( settings . useTabOnHangingIndent ) { snippetCursor = '$1' ; } let hanging = Hanging . None ; let toInsert = '\n' ; try { if ( textEditor . document . languageId === 'python' ) { const lines = textEditor . document . getText ( new vscode . Range ( 0 , 0 , position . line , position . character ) ) . split ( "\n" ) ; const edits = editsToMake ( lines , currentLine , tabSize , position . line , position . character , settings . trimLinesWithOnlyWhitespace , settings . keepHangingBracketOnLine ) ; toInsert = edits . insert ; edits . deletes . forEach ( range => { edit . delete ( range ) ; } ) ; hanging = edits . hanging ; } } finally { if ( hanging === Hanging . Full ) { textEditor . insertSnippet ( new vscode . SnippetString ( '\n' + ' ' . repeat ( tabSize ) + snippetCursor + '\n' ) ) ; } else { edit . insert ( insertionPoint , toInsert ) ; } textEditor . revealRange ( new vscode . Range ( position , new vscode . Position ( position . line + 2 , 0 ) ) ) ; } } export function editsToMake ( lines , currentLine , tabSize , lineNum , charNum , trimLinesWithOnlyWhitespace , keepHangingBracketOnLine ) { let { nextIndentationLevel : indent } = indentationInfo ( lines , tabSize ) ; let deletes = [ ] ; const numCharsToDelete = startingWhitespaceLength ( currentLine . slice ( charNum ) ) ; if ( ( numCharsToDelete > 0 ) && ( /\S/ . test ( currentLine . slice ( 0 , charNum ) ) ) ) { deletes . push ( new vscode . Range ( lineNum , charNum , lineNum , charNum + numCharsToDelete ) ) ; } const dedentAmount = currentLineDedentation ( lines , tabSize ) ; const shouldTrim = trimCurrentLine ( lines [ lines . length - 1 ] , trimLinesWithOnlyWhitespace ) ; if ( ( dedentAmount > 0 ) || shouldTrim ) { const totalDeleteAmount = shouldTrim ? lines [ lines . length - 1 ] . length : dedentAmount ; deletes . push ( new vscode . Range ( lineNum , 0 , lineNum , totalDeleteAmount ) ) ; indent = Math . max ( indent - dedentAmount , 0 ) ; } let hanging = shouldHang ( currentLine , charNum ) ; if ( keepHangingBracketOnLine && hanging === Hanging . Full ) { hanging = Hanging . Partial ; } let toInsert = '\n' ; if ( hanging === Hanging . Partial ) { toInsert = '\n' + ' ' . repeat ( indentationLevel ( currentLine ) + tabSize ) ; } else { toInsert = '\n' + ' ' . repeat ( Math . max ( indent , 0 ) ) ; } if ( extendCommentToNextLine ( currentLine , charNum ) ) { toInsert = toInsert + '# ' ; } return { insert : toInsert , deletes : deletes , hanging : hanging } ; } export function extendCommentToNextLine ( line , pos ) { if ( line . trim ( ) . startsWith ( '#' ) && line . slice ( pos ) . trim ( ) . length && line . slice ( 0 , pos ) . trim ( ) . length ) { return true ; } return false ; } export function currentLineDedentation ( lines , tabSize ) { const dedentKeywords = { elif : [ "if" ] , else : [ "if" , "try" , "for" , "while" ] , except : [ "try" ] , finally : [ "try" ] } ; lines = lines . slice ( ) . reverse ( ) ; const line = lines [ 0 ] ; const trimmed = line . trim ( ) ; if ( trimmed . endsWith ( ":" ) ) { for ( const keyword of Object . keys ( dedentKeywords ) . filter ( ( key ) => trimmed . startsWith ( key ) ) ) { for ( const matchedLine of lines . slice ( 1 ) . filter ( ( l ) => l . trim ( ) . endsWith ( ":" ) ) ) { const matchedLineTrimmed = matchedLine . trim ( ) ; if ( dedentKeywords [ keyword ] . some ( ( matcher ) => matchedLineTrimmed . startsWith ( matcher ) ) ) { const currentIndent = indentationLevel ( line ) ; const matchedIndent = indentationLevel ( matchedLine ) ; return Math . max ( 0 , Math . min ( tabSize , currentIndent , currentIndent - matchedIndent ) ) ; } } } } return 0 ; } export function trimCurrentLine ( line , trimLinesWithOnlyWhitespace ) { if ( trimLinesWithOnlyWhitespace ) { if ( line . trim ( ) . length === 0 ) { return true ; } } return false ; } export function startingWhitespaceLength ( line ) { return /\S/ . exec ( line ) ?. index ?? 0 ; }