"use strict" ; import { commands , ExtensionContext , Position , Range , Selection , TextEditor , window , workspace , WorkspaceEdit } from "vscode" ; import { isInFencedCodeBlock , mathEnvCheck } from "../utils" ; export function activate ( context ) { context . subscriptions . push ( commands . registerCommand ( "rmarkdown.onEnterKey" , onEnterKey ) , commands . registerCommand ( "rmarkdown.onCtrlEnterKey" , ( ) => { return onEnterKey ( "ctrl" ) ; } ) , commands . registerCommand ( "rmarkdown.onShiftEnterKey" , ( ) => { return onEnterKey ( "shift" ) ; } ) , commands . registerCommand ( "rmarkdown.onTabKey" , onTabKey ) , commands . registerCommand ( "rmarkdown.onShiftTabKey" , ( ) => { return onTabKey ( "shift" ) ; } ) , commands . registerCommand ( "rmarkdown.onBackspaceKey" , onBackspaceKey ) , commands . registerCommand ( "rmarkdown.checkTaskList" , checkTaskList ) , commands . registerCommand ( "rmarkdown.onMoveLineDown" , onMoveLineDown ) , commands . registerCommand ( "rmarkdown.onMoveLineUp" , onMoveLineUp ) , commands . registerCommand ( "rmarkdown.onCopyLineDown" , onCopyLineDown ) , commands . registerCommand ( "rmarkdown.onCopyLineUp" , onCopyLineUp ) , commands . registerCommand ( "rmarkdown.onIndentLines" , onIndentLines ) , commands . registerCommand ( "rmarkdown.onOutdentLines" , onOutdentLines ) ) ; } function onEnterKey ( modifiers ? ) { let editor = window . activeTextEditor ! ; let cursorPos = editor . selection . active ; let line = editor . document . lineAt ( cursorPos . line ) ; let textBeforeCursor = line . text . substr ( 0 , cursorPos . character ) ; let textAfterCursor = line . text . substr ( cursorPos . character ) ; let lineBreakPos = cursorPos ; if ( modifiers === "ctrl" ) { lineBreakPos = line . range . end ; } if ( modifiers === "shift" || isInFencedCodeBlock ( editor . document , cursorPos . line ) || mathEnvCheck ( editor . document , cursorPos ) ) { return asNormal ( "enter" , modifiers ) ; } if ( /^(>|([-+*]|[0-9]+[.)])( +\[[ x]\])?)$/ . test ( textBeforeCursor . trim ( ) ) && textAfterCursor . trim ( ) . length === 0 ) { return editor . edit ( ( editBuilder ) => { editBuilder . delete ( line . range ) ; editBuilder . insert ( line . range . end , "\n" ) ; } ) . then ( ( ) => { editor . revealRange ( editor . selection ) ; } ) . then ( ( ) => fixMarker ( findNextMarkerLineNumber ( ) ) ) ; } let matches ; if ( /^> / . test ( textBeforeCursor ) ) { return editor . edit ( ( editBuilder ) => { editBuilder . insert ( lineBreakPos , `\n> ` ) ; } ) . then ( ( ) => { if ( modifiers === "ctrl" && ! cursorPos . isEqual ( lineBreakPos ) ) { let newCursorPos = cursorPos . with ( line . lineNumber + 1 , 2 ) ; editor . selection = new Selection ( newCursorPos , newCursorPos ) ; } } ) . then ( ( ) => { editor . revealRange ( editor . selection ) ; } ) ; } else if ( ( matches = /^(\s*[-+*] +(\[[ x]\] +)?)/ . exec ( textBeforeCursor ) ) !== null ) { return editor . edit ( ( editBuilder ) => { editBuilder . insert ( lineBreakPos , `\n${ matches ! [ 1 ] . replace ( "[x]" , "[ ]" ) }` ) ; } ) . then ( ( ) => { if ( modifiers === "ctrl" && ! cursorPos . isEqual ( lineBreakPos ) ) { let newCursorPos = cursorPos . with ( line . lineNumber + 1 , matches ! [ 1 ] . length ) ; editor . selection = new Selection ( newCursorPos , newCursorPos ) ; } } ) . then ( ( ) => { editor . revealRange ( editor . selection ) ; } ) ; } else if ( ( matches = /^(\s*)([0-9]+)([.)])( +)((\[[ x]\] +)?)/ . exec ( textBeforeCursor ) ) !== null ) { let config = workspace . getConfiguration ( "rmarkdown.orderedList" ) . get < string > ( "marker" ) ; let marker = "1" ; let leadingSpace = matches [ 1 ] ; let previousMarker = matches [ 2 ] ; let delimiter = matches [ 3 ] ; let trailingSpace = matches [ 4 ] ; let gfmCheckbox = matches [ 5 ] . replace ( "[x]" , "[ ]" ) ; let textIndent = ( previousMarker + delimiter + trailingSpace ) . length ; if ( config === "ordered" ) { marker = String ( Number ( previousMarker ) + 1 ) ; } trailingSpace = " " . repeat ( Math . max ( 1 , textIndent - ( marker + delimiter ) . length ) ) ; const toBeAdded = leadingSpace + marker + delimiter + trailingSpace + gfmCheckbox ; return editor . edit ( ( editBuilder ) => { editBuilder . insert ( lineBreakPos , `\n${ toBeAdded }` ) ; } , { undoStopBefore : true , undoStopAfter : false } ) . then ( ( ) => { if ( modifiers === "ctrl" && ! cursorPos . isEqual ( lineBreakPos ) ) { let newCursorPos = cursorPos . with ( line . lineNumber + 1 , toBeAdded . length ) ; editor . selection = new Selection ( newCursorPos , newCursorPos ) ; } } ) . then ( ( ) => fixMarker ( ) ) . then ( ( ) => { editor . revealRange ( editor . selection ) ; } ) ; } else { return asNormal ( "enter" , modifiers ) ; } } function onTabKey ( modifiers ? ) { let editor = window . activeTextEditor ! ; let cursorPos = editor . selection . start ; let lineText = editor . document . lineAt ( cursorPos . line ) . text ; if ( isInFencedCodeBlock ( editor . document , cursorPos . line ) || mathEnvCheck ( editor . document , cursorPos ) ) { return asNormal ( "tab" , modifiers ) ; } let match = /^\s*([-+*]|[0-9]+[.)]) +(\[[ x]\] +)?/ . exec ( lineText ) ; if ( match && ( modifiers === "shift" || ! editor . selection . isEmpty || ( editor . selection . isEmpty && cursorPos . character <= match [ 0 ] . length ) ) ) { if ( modifiers === "shift" ) { return outdent ( editor ) . then ( ( ) => fixMarker ( ) ) ; } else { return indent ( editor ) . then ( ( ) => fixMarker ( ) ) ; } } else { return asNormal ( "tab" , modifiers ) ; } } function onBackspaceKey ( ) { let editor = window . activeTextEditor ! ; let cursor = editor . selection . active ; let document = editor . document ; let textBeforeCursor = document . lineAt ( cursor . line ) . text . substr ( 0 , cursor . character ) ; if ( isInFencedCodeBlock ( document , cursor . line ) || mathEnvCheck ( editor . document , cursor ) ) { return asNormal ( "backspace" ) ; } if ( ! editor . selection . isEmpty ) { return asNormal ( "backspace" ) ! . then ( ( ) => fixMarker ( findNextMarkerLineNumber ( ) ) ) ; } else if ( /^\s+([-+*]|[0-9]+[.)]) $/ . test ( textBeforeCursor ) ) { return outdent ( editor ) . then ( ( ) => fixMarker ( ) ) ; } else if ( /^([-+*]|[0-9]+[.)]) $/ . test ( textBeforeCursor ) ) { return editor . edit ( ( editBuilder ) => { editBuilder . replace ( new Range ( cursor . with ( { character : 0 } ) , cursor ) , " " . repeat ( textBeforeCursor . length ) ) ; } ) . then ( ( ) => fixMarker ( findNextMarkerLineNumber ( ) ) ) ; } else if ( /^\s*([-+*]|[0-9]+[.)]) +(\[[ x]\] )$/ . test ( textBeforeCursor ) ) { return deleteRange ( editor , new Range ( cursor . with ( { character : textBeforeCursor . length - 4 } ) , cursor ) ) . then ( ( ) => fixMarker ( findNextMarkerLineNumber ( ) ) ) ; } else { return asNormal ( "backspace" ) ; } } function asNormal ( key , modifiers ? ) { switch ( key ) { case "enter" : if ( modifiers === "ctrl" ) { return commands . executeCommand ( "editor.action.insertLineAfter" ) ; } else { return commands . executeCommand ( "type" , { source : "keyboard" , text : "\n" } ) ; } case "tab" : if ( modifiers === "shift" ) { return commands . executeCommand ( "editor.action.outdentLines" ) ; } else if ( window . activeTextEditor ! . selection . isEmpty && workspace . getConfiguration ( "emmet" ) . get < boolean > ( "triggerExpansionOnTab" ) ) { return commands . executeCommand ( "editor.emmet.action.expandAbbreviation" ) ; } else { return commands . executeCommand ( "tab" ) ; } case "backspace" : return commands . executeCommand ( "deleteLeft" ) ; } } /**
 * If
 *
 * 1. it is not the first line
 * 2. there is a Markdown list item before this line
 *
 * then indent the current line to align with the previous list item.
 */ function indent ( editor ? ) { if ( ! editor ) { editor = window . activeTextEditor ! ; } if ( workspace . getConfiguration ( "rmarkdown.list" , editor . document . uri ) . get < string > ( "indentationSize" ) === "adaptive" ) { try { const selection = editor . selection ; const indentationSize = tryDetermineIndentationSize ( editor , selection . start . line , editor . document . lineAt ( selection . start . line ) . firstNonWhitespaceCharacterIndex ) ; let edit = new WorkspaceEdit ( ) ; for ( let i = selection . start . line ; i <= selection . end . line ; i ++ ) { if ( i === selection . end . line && ! selection . isEmpty && selection . end . character === 0 ) { break ; } if ( editor . document . lineAt ( i ) . text . length !== 0 ) { edit . insert ( editor . document . uri , new Position ( i , 0 ) , " " . repeat ( indentationSize ) ) ; } } return workspace . applyEdit ( edit ) ; } catch ( error ) { } } return commands . executeCommand ( "editor.action.indentLines" ) ; } /**
 * Similar to `indent`-function
 */ function outdent ( editor ? ) { if ( ! editor ) { editor = window . activeTextEditor ! ; } if ( workspace . getConfiguration ( "rmarkdown.list" , editor . document . uri ) . get < string > ( "indentationSize" ) === "adaptive" ) { try { const selection = editor . selection ; const indentationSize = tryDetermineIndentationSize ( editor , selection . start . line , editor . document . lineAt ( selection . start . line ) . firstNonWhitespaceCharacterIndex ) ; let edit = new WorkspaceEdit ( ) ; for ( let i = selection . start . line ; i <= selection . end . line ; i ++ ) { if ( i === selection . end . line && ! selection . isEmpty && selection . end . character === 0 ) { break ; } const lineText = editor . document . lineAt ( i ) . text ; let maxOutdentSize ; if ( lineText . trim ( ) . length === 0 ) { maxOutdentSize = lineText . length ; } else { maxOutdentSize = editor . document . lineAt ( i ) . firstNonWhitespaceCharacterIndex ; } if ( maxOutdentSize > 0 ) { edit . delete ( editor . document . uri , new Range ( i , 0 , i , Math . min ( indentationSize , maxOutdentSize ) ) ) ; } } return workspace . applyEdit ( edit ) ; } catch ( error ) { } } return commands . executeCommand ( "editor.action.outdentLines" ) ; } function tryDetermineIndentationSize ( editor , line , currentIndentation ) { while ( -- line >= 0 ) { const lineText = editor . document . lineAt ( line ) . text ; let matches ; if ( ( matches = /^(\s*)(([-+*]|[0-9]+[.)]) +)(\[[ x]\] +)?/ . exec ( lineText ) ) !== null ) { if ( matches [ 1 ] . length <= currentIndentation ) { return matches [ 2 ] . length ; } } } throw Error ( "No previous Markdown list item" ) ; } /**
 * Returns the line number of the next ordered list item starting either from
 * the specified line or the beginning of the current selection.
 */ function findNextMarkerLineNumber ( line ? ) { let editor = window . activeTextEditor ! ; if ( line === undefined ) { line = editor . selection . start . line ; } while ( line < editor . document . lineCount ) { const lineText = editor . document . lineAt ( line ) . text ; if ( lineText . startsWith ( "#" ) ) { return - 1 ; } if ( /^\s*[0-9]+[.)] +/ . exec ( lineText ) !== null ) { return line ; } line ++ ; } return undefined ; } returns function lookUpwardForMarker ( editor , line , currentIndentation ) { while ( -- line >= 0 ) { const lineText = editor . document . lineAt ( line ) . text ; let matches ; if ( ( matches = /^(\s*)(([0-9]+)[.)] +)/ . exec ( lineText ) ) !== null ) { let leadingSpace = matches [ 1 ] ; let marker = matches [ 3 ] ; if ( leadingSpace . length === currentIndentation ) { return Number ( marker ) + 1 ; } else if ( ( ! leadingSpace . includes ( "\t" ) && leadingSpace . length + matches [ 2 ] . length <= currentIndentation ) || ( leadingSpace . includes ( "\t" ) && leadingSpace . length + 1 <= currentIndentation ) ) { return 1 ; } } else if ( ( matches = /^(\s*)\S/ . exec ( lineText ) ) !== null ) { if ( matches [ 1 ] . length <= currentIndentation ) { break ; } } } return 1 ; } /**
 * Fix ordered list marker *iteratively* starting from current line
 */ export function fixMarker ( line ? ) { if ( ! workspace . getConfiguration ( "rmarkdown.orderedList" ) . get < boolean > ( "autoRenumber" ) ) { return ; } if ( workspace . getConfiguration ( "rmarkdown.orderedList" ) . get < string > ( "marker" ) === "one" ) { return ; } let editor = window . activeTextEditor ! ; if ( line === undefined ) { line = findNextMarkerLineNumber ( ) ; if ( line === undefined || line > editor . selection . end . line ) { line = editor . selection . active . line ; } } if ( line < 0 || editor . document . lineCount <= line ) { return ; } let currentLineText = editor . document . lineAt ( line ) . text ; let matches ; if ( ( matches = /^(\s*)([0-9]+)([.)])( +)/ . exec ( currentLineText ) ) !== null ) { let leadingSpace = matches [ 1 ] ; let marker = matches [ 2 ] ; let delimiter = matches [ 3 ] ; let trailingSpace = matches [ 4 ] ; let fixedMarker = lookUpwardForMarker ( editor , line , leadingSpace . length ) ; let listIndent = marker . length + delimiter . length + trailingSpace . length ; let fixedMarkerString = String ( fixedMarker ) ; return editor . edit ( ( editBuilder ) => { if ( marker === fixedMarkerString ) { return ; } fixedMarkerString += delimiter + " " . repeat ( Math . max ( 1 , listIndent - ( fixedMarkerString + delimiter ) . length ) ) ; editBuilder . replace ( new Range ( line ! , leadingSpace . length , line ! , leadingSpace . length + listIndent ) , fixedMarkerString ) ; } , { undoStopBefore : false , undoStopAfter : false } ) . then ( ( ) => { let nextLine = line ! + 1 ; let indentString = " " . repeat ( listIndent ) ; while ( editor . document . lineCount > nextLine ) { const nextLineText = editor . document . lineAt ( nextLine ) . text ; if ( /^\s*[0-9]+[.)] +/ . test ( nextLineText ) ) { return fixMarker ( nextLine ) ; } else if ( /^\s*$/ . test ( nextLineText ) ) { nextLine ++ ; } else if ( listIndent <= 4 && ! nextLineText . startsWith ( indentString ) ) { return ; } else { nextLine ++ ; } } } ) ; } } function deleteRange ( editor , range ) { return editor . edit ( ( editBuilder ) => { editBuilder . delete ( range ) ; } , { undoStopBefore : true , undoStopAfter : false } ) ; } function checkTaskList ( ) { let editor = window . activeTextEditor ! ; const uncheckedRegex = /^(\s*([-+*]|[0-9]+[.)]) +\[) \]/ ; const checkedRegex = /^(\s*([-+*]|[0-9]+[.)]) +\[)x\]/ ; var toBeToggled = [ ] ; var newState = undefined ; for ( const selection of editor . selections ) { for ( let i = selection . start . line ; i <= selection . end . line ; i ++ ) { let line = editor . document . lineAt ( i ) ; let lineStart = line . range . start ; let matches ; if ( ( matches = uncheckedRegex . exec ( line . text ) ) && newState !== false ) { toBeToggled . push ( lineStart . with ( { character : matches [ 1 ] . length } ) ) ; newState = true ; } else if ( ( matches = checkedRegex . exec ( line . text ) ) && newState !== true ) { toBeToggled . push ( lineStart . with ( { character : matches [ 1 ] . length } ) ) ; newState = false ; } } } if ( newState !== undefined ) { const newChar = newState ? "x" : " " ; return editor . edit ( ( editBuilder ) => { for ( const pos of toBeToggled ) { var range = new Range ( pos , pos . with ( { character : pos . character + 1 } ) ) ; editBuilder . replace ( range , newChar ) ; } } ) ; } } function onMoveLineUp ( ) { return commands . executeCommand ( "editor.action.moveLinesUpAction" ) . then ( ( ) => fixMarker ( ) ) ; } function onMoveLineDown ( ) { return commands . executeCommand ( "editor.action.moveLinesDownAction" ) . then ( ( ) => fixMarker ( findNextMarkerLineNumber ( window . activeTextEditor ! . selection . start . line - 1 ) ) ) ; } function onCopyLineUp ( ) { return commands . executeCommand ( "editor.action.copyLinesUpAction" ) . then ( ( ) => fixMarker ( ) ) ; } function onCopyLineDown ( ) { return commands . executeCommand ( "editor.action.copyLinesDownAction" ) . then ( ( ) => fixMarker ( ) ) ; } function onIndentLines ( ) { return indent ( ) . then ( ( ) => fixMarker ( ) ) ; } function onOutdentLines ( ) { return outdent ( ) . then ( ( ) => fixMarker ( ) ) ; } export function deactivate ( ) { }