2020-06-30 22:58:03 +03:00
import * as vscode from 'vscode' ;
2023-12-23 04:16:43 +03:00
import { ExCommandLine , SearchCommandLine } from './src/cmd_line/commandLine' ;
import { configuration } from './src/configuration/configuration' ;
import { Notation } from './src/configuration/notation' ;
2020-06-30 22:58:03 +03:00
import { Globals } from './src/globals' ;
import { Jump } from './src/jumps/jump' ;
2023-12-23 04:16:43 +03:00
import { Mode } from './src/mode/mode' ;
2020-06-30 22:58:03 +03:00
import { ModeHandler } from './src/mode/modeHandler' ;
import { ModeHandlerMap } from './src/mode/modeHandlerMap' ;
2021-10-21 06:29:15 +03:00
import { Register } from './src/register/register' ;
2023-12-23 04:16:43 +03:00
import { CompositionState } from './src/state/compositionState' ;
import { globalState } from './src/state/globalState' ;
import { StatusBar } from './src/statusBar' ;
import { taskQueue } from './src/taskQueue' ;
import { Logger } from './src/util/logger' ;
Overhaul remapping logic (#4735)
This is a pretty massive change; see pull request #4735 for full details
Most notably:
- Support for operator-pending mode, including remaps and a half-cursor decoration
- Correct handling of ambiguous remaps with timeout
- Correct handling of recursive special case when the RHS starts with the LHS
- Correct handling of multi-key remaps in insert mode
- Failed movements that occur partway through a remap stop & discard the rest of the remap
- Implement `unmap` and `mapclear` in .vimrc
Refs #463, refs #4908
Fixes #1261, fixes #1398, fixes #1579, fixes #1821, fixes #1835
Fixes #1870, fixes #1883, fixes #2041, fixes #2234, fixes #2466
Fixes #2897, fixes #2955, fixes #2975, fixes #3082, fixes #3086
Fixes #3171, fixes #3373, fixes #3413, fixes #3742, fixes #3768
Fixes #3988, fixes #4057, fixes #4118, fixes #4236, fixes #4353
Fixes #4464, fixes #4530, fixes #4532, fixes #4563, fixes #4674
Fixes #4756, fixes #4883, fixes #4928, fixes #4991, fixes #5016
Fixes #5057, fixes #5067, fixes #5084, fixes #5125
2020-08-16 21:22:51 +03:00
import { SpecialKeys } from './src/util/specialKeys' ;
2023-12-23 04:16:43 +03:00
import { VSCodeContext } from './src/util/vscodeContext' ;
2022-12-06 06:08:15 +03:00
import { exCommandParser } from './src/vimscript/exCommandParser' ;
2020-06-30 22:58:03 +03:00
let extensionContext : vscode.ExtensionContext ;
2021-12-10 06:38:53 +03:00
let previousActiveEditorUri : vscode.Uri | undefined ;
2020-06-30 22:58:03 +03:00
let lastClosedModeHandler : ModeHandler | null = null ;
interface ICodeKeybinding {
after? : string [ ] ;
2021-03-06 03:40:13 +03:00
commands? : Array < { command : string ; args : any [ ] } > ;
2020-06-30 22:58:03 +03:00
}
2020-11-16 22:55:44 +03:00
export async function getAndUpdateModeHandler (
2023-09-10 02:57:26 +03:00
forceSyncAndUpdate = false ,
2020-11-16 22:55:44 +03:00
) : Promise < ModeHandler | undefined > {
2020-06-30 22:58:03 +03:00
const activeTextEditor = vscode . window . activeTextEditor ;
2020-11-19 03:08:16 +03:00
if ( activeTextEditor === undefined || activeTextEditor . document . isClosed ) {
2020-11-16 22:55:44 +03:00
return undefined ;
}
2022-03-09 06:11:36 +03:00
const [ curHandler , isNew ] = await ModeHandlerMap . getOrCreate ( activeTextEditor ) ;
2020-06-30 22:58:03 +03:00
if ( isNew ) {
extensionContext . subscriptions . push ( curHandler ) ;
}
2020-11-16 22:55:44 +03:00
curHandler . vimState . editor = activeTextEditor ;
2020-06-30 22:58:03 +03:00
2022-03-09 06:11:36 +03:00
if (
forceSyncAndUpdate ||
! previousActiveEditorUri ||
previousActiveEditorUri !== activeTextEditor . document . uri
) {
2020-06-30 22:58:03 +03:00
// We sync the cursors here because ModeHandler is specific to a document, not an editor, so we
// need to update our representation of the cursors when switching between editors for the same document.
// This will be unnecessary once #4889 is fixed.
curHandler . syncCursors ( ) ;
2020-08-17 21:32:58 +03:00
await curHandler . updateView ( { drawSelection : false , revealRange : false } ) ;
2020-06-30 22:58:03 +03:00
}
2022-03-09 06:11:36 +03:00
previousActiveEditorUri = activeTextEditor . document . uri ;
2020-06-30 22:58:03 +03:00
2021-08-31 04:39:05 +03:00
if ( curHandler . focusChanged ) {
curHandler . focusChanged = false ;
2020-06-30 22:58:03 +03:00
2021-12-10 06:38:53 +03:00
if ( previousActiveEditorUri ) {
const prevHandler = ModeHandlerMap . get ( previousActiveEditorUri ) ;
2021-08-31 04:39:05 +03:00
prevHandler ! . focusChanged = true ;
2020-06-30 22:58:03 +03:00
}
}
return curHandler ;
}
/ * *
* Loads and validates the user ' s configuration
* /
2021-07-23 19:55:20 +03:00
async function loadConfiguration() {
const validatorResults = await configuration . load ( ) ;
2020-11-03 09:05:06 +03:00
2022-12-30 05:58:08 +03:00
Logger . debug ( ` ${ validatorResults . numErrors } errors found with vim configuration ` ) ;
2020-06-30 22:58:03 +03:00
if ( validatorResults . numErrors > 0 ) {
2021-03-06 03:40:13 +03:00
for ( const validatorResult of validatorResults . get ( ) ) {
2020-06-30 22:58:03 +03:00
switch ( validatorResult . level ) {
case 'error' :
2022-12-30 05:58:08 +03:00
Logger . error ( validatorResult . message ) ;
2020-06-30 22:58:03 +03:00
break ;
case 'warning' :
2022-12-30 05:58:08 +03:00
Logger . warn ( validatorResult . message ) ;
2020-06-30 22:58:03 +03:00
break ;
}
}
}
}
/ * *
* The extension ' s entry point
* /
2020-12-07 20:18:49 +03:00
export async function activate ( context : vscode.ExtensionContext , handleLocal : boolean = true ) {
2022-12-06 06:08:15 +03:00
ExCommandLine . parser = exCommandParser ;
2022-12-30 05:58:08 +03:00
Logger . init ( ) ;
2020-06-30 22:58:03 +03:00
// before we do anything else, we need to load the configuration
await loadConfiguration ( ) ;
2022-12-30 05:58:08 +03:00
Logger . debug ( 'Start' ) ;
2020-06-30 22:58:03 +03:00
extensionContext = context ;
extensionContext . subscriptions . push ( StatusBar ) ;
2020-08-17 02:49:22 +03:00
// Load state
2020-12-07 20:18:49 +03:00
Register . loadFromDisk ( handleLocal ) ;
2021-10-21 06:29:15 +03:00
await Promise . all ( [ ExCommandLine . loadHistory ( context ) , SearchCommandLine . loadHistory ( context ) ] ) ;
2020-08-17 02:49:22 +03:00
2020-06-30 22:58:03 +03:00
if ( vscode . window . activeTextEditor ) {
const filepathComponents = vscode . window . activeTextEditor . document . fileName . split ( /\\|\// ) ;
2021-04-10 05:29:49 +03:00
Register . setReadonlyRegister ( '%' , filepathComponents [ filepathComponents . length - 1 ] ) ;
2020-06-30 22:58:03 +03:00
}
// workspace events
registerEventListener (
context ,
vscode . workspace . onDidChangeConfiguration ,
2021-07-23 19:55:20 +03:00
async ( ) = > {
2023-02-16 07:22:59 +03:00
Logger . info ( 'Configuration changed' ) ;
2021-07-23 19:55:20 +03:00
await loadConfiguration ( ) ;
2020-06-30 22:58:03 +03:00
} ,
2023-09-10 02:57:26 +03:00
false ,
2020-06-30 22:58:03 +03:00
) ;
registerEventListener ( context , vscode . workspace . onDidChangeTextDocument , async ( event ) = > {
2023-02-16 07:22:59 +03:00
if ( event . document . uri . scheme === 'output' ) {
// Without this, we'll get an infinite logging loop
return ;
}
if ( event . contentChanges . length === 0 ) {
// This happens when the document is saved
return ;
}
Logger . debug (
2023-09-10 02:57:26 +03:00
` ${ event . contentChanges . length } change(s) to ${ event . document . fileName } because ${ event . reason } ` ,
2023-02-16 07:22:59 +03:00
) ;
for ( const x of event . contentChanges ) {
Logger . trace ( ` \ t- ${ x . rangeLength } , +' ${ x . text } ' ` ) ;
}
2023-03-22 06:46:54 +03:00
if ( event . contentChanges . length === 1 ) {
const change = event . contentChanges [ 0 ] ;
const anyLinesDeleted = change . range . start . line !== change . range . end . line ;
if ( anyLinesDeleted && change . text === '' ) {
globalState . jumpTracker . handleTextDeleted ( event . document , change . range ) ;
} else if ( ! anyLinesDeleted && change . text . includes ( '\n' ) ) {
globalState . jumpTracker . handleTextAdded ( event . document , change . range , change . text ) ;
} else {
// TODO: What to do here?
}
2022-12-30 09:29:42 +03:00
} else {
// TODO: In this case, we should probably loop over the content changes...
2020-06-30 22:58:03 +03:00
}
// Change from VSCode editor should set document.isDirty to true but they initially don't!
// There is a timing issue in VSCode codebase between when the isDirty flag is set and
// when registered callbacks are fired. https://github.com/Microsoft/vscode/issues/11339
const contentChangeHandler = ( modeHandler : ModeHandler ) = > {
if ( modeHandler . vimState . currentMode === Mode . Insert ) {
if ( modeHandler . vimState . historyTracker . currentContentChanges === undefined ) {
modeHandler . vimState . historyTracker . currentContentChanges = [ ] ;
}
2021-05-10 08:29:40 +03:00
modeHandler . vimState . historyTracker . currentContentChanges =
modeHandler . vimState . historyTracker . currentContentChanges . concat ( event . contentChanges ) ;
2020-06-30 22:58:03 +03:00
}
} ;
2023-03-17 03:16:05 +03:00
const mh = ModeHandlerMap . get ( event . document . uri ) ;
if ( mh ) {
contentChangeHandler ( mh ) ;
}
2020-06-30 22:58:03 +03:00
} ) ;
registerEventListener (
context ,
vscode . workspace . onDidCloseTextDocument ,
async ( closedDocument ) = > {
2023-02-16 07:22:59 +03:00
Logger . info ( ` ${ closedDocument . fileName } closed ` ) ;
2020-06-30 22:58:03 +03:00
// Delete modehandler once all tabs of this document have been closed
2023-03-17 03:16:05 +03:00
for ( const [ uri , modeHandler ] of ModeHandlerMap . entries ( ) ) {
2020-06-30 22:58:03 +03:00
let shouldDelete = false ;
2022-03-09 06:11:36 +03:00
if ( modeHandler == null ) {
2020-06-30 22:58:03 +03:00
shouldDelete = true ;
} else {
2020-11-17 00:04:45 +03:00
const document = modeHandler . vimState . document ;
2023-02-16 07:22:59 +03:00
if ( ! vscode . workspace . textDocuments . includes ( document ) ) {
2020-06-30 22:58:03 +03:00
shouldDelete = true ;
if ( closedDocument === document ) {
lastClosedModeHandler = modeHandler ;
}
}
}
if ( shouldDelete ) {
2021-12-10 06:38:53 +03:00
ModeHandlerMap . delete ( uri ) ;
2020-06-30 22:58:03 +03:00
}
}
} ,
2023-09-10 02:57:26 +03:00
false ,
2020-06-30 22:58:03 +03:00
) ;
// window events
registerEventListener (
context ,
vscode . window . onDidChangeActiveTextEditor ,
2023-02-16 07:22:59 +03:00
async ( activeTextEditor : vscode.TextEditor | undefined ) = > {
if ( activeTextEditor ) {
Logger . info ( ` Active editor: ${ activeTextEditor . document . uri } ` ) ;
} else {
Logger . debug ( ` No active editor ` ) ;
}
2021-12-10 06:38:53 +03:00
const mhPrevious : ModeHandler | undefined = previousActiveEditorUri
? ModeHandlerMap . get ( previousActiveEditorUri )
2020-06-30 22:58:03 +03:00
: undefined ;
// Track the closed editor so we can use it the next time an open event occurs.
// When vscode changes away from a temporary file, onDidChangeActiveTextEditor first twice.
// First it fires when leaving the closed editor. Then onDidCloseTextDocument first, and we delete
// the old ModeHandler. Then a new editor opens.
//
// This also applies to files that are merely closed, which allows you to jump back to that file similarly
// once a new file is opened.
lastClosedModeHandler = mhPrevious || lastClosedModeHandler ;
2021-04-10 05:29:49 +03:00
const oldFileRegister = ( await Register . get ( '%' ) ) ? . text ;
2022-03-09 07:33:19 +03:00
const relativePath = activeTextEditor
? vscode . workspace . asRelativePath ( activeTextEditor . document . uri , false )
: '' ;
2021-03-17 02:37:22 +03:00
if ( relativePath !== oldFileRegister ) {
if ( oldFileRegister && oldFileRegister !== '' ) {
2021-04-10 05:29:49 +03:00
Register . setReadonlyRegister ( '#' , oldFileRegister as string ) ;
2021-03-17 02:37:22 +03:00
}
2021-04-10 05:29:49 +03:00
Register . setReadonlyRegister ( '%' , relativePath ) ;
2021-03-17 02:37:22 +03:00
}
2020-06-30 22:58:03 +03:00
2022-08-02 05:23:42 +03:00
if ( activeTextEditor === undefined ) {
return ;
}
2023-04-21 04:12:08 +03:00
2023-09-26 05:48:47 +03:00
taskQueue . enqueueTask ( async ( ) = > {
const mh = await getAndUpdateModeHandler ( true ) ;
if ( mh ) {
globalState . jumpTracker . handleFileJump (
lastClosedModeHandler ? Jump . fromStateNow ( lastClosedModeHandler . vimState ) : null ,
Jump . fromStateNow ( mh . vimState ) ,
) ;
}
} ) ;
2020-06-30 22:58:03 +03:00
} ,
true ,
2023-09-10 02:57:26 +03:00
true ,
2020-06-30 22:58:03 +03:00
) ;
registerEventListener (
context ,
vscode . window . onDidChangeTextEditorSelection ,
async ( e : vscode.TextEditorSelectionChangeEvent ) = > {
2023-03-16 22:10:39 +03:00
if ( e . textEditor . document . uri . scheme === 'output' ) {
// Without this, we can an infinite logging loop
return ;
}
2020-06-30 22:58:03 +03:00
if (
vscode . window . activeTextEditor === undefined ||
e . textEditor . document !== vscode . window . activeTextEditor . document
) {
2020-11-16 22:55:44 +03:00
// We don't care if user selection changed in a paneled window (e.g debug console/terminal)
2020-06-30 22:58:03 +03:00
return ;
}
2021-12-10 06:38:53 +03:00
const mh = ModeHandlerMap . get ( vscode . window . activeTextEditor . document . uri ) ;
2020-11-16 22:55:44 +03:00
if ( mh === undefined ) {
// We don't care if there is no active editor
return ;
}
2020-06-30 22:58:03 +03:00
2020-09-26 00:17:44 +03:00
if ( e . kind !== vscode . TextEditorSelectionChangeKind . Mouse ) {
const selectionsHash = e . selections . reduce (
( hash , s ) = >
hash +
` [ ${ s . anchor . line } , ${ s . anchor . character } ; ${ s . active . line } , ${ s . active . character } ] ` ,
2023-09-10 02:57:26 +03:00
'' ,
2020-07-25 21:45:52 +03:00
) ;
2022-03-14 07:19:31 +03:00
const idx = mh . selectionsChanged . ourSelections . indexOf ( selectionsHash ) ;
2020-09-26 00:17:44 +03:00
if ( idx > - 1 ) {
2022-03-14 07:19:31 +03:00
mh . selectionsChanged . ourSelections . splice ( idx , 1 ) ;
2023-02-16 07:22:59 +03:00
Logger . trace (
2023-09-10 02:57:26 +03:00
` Ignoring selection: ${ selectionsHash } . ${ mh . selectionsChanged . ourSelections . length } left ` ,
2020-09-26 00:17:44 +03:00
) ;
return ;
2022-03-14 07:19:31 +03:00
} else if ( mh . selectionsChanged . ignoreIntermediateSelections ) {
2023-02-16 07:22:59 +03:00
Logger . trace ( ` Ignoring intermediate selection change: ${ selectionsHash } ` ) ;
2020-09-26 00:17:44 +03:00
return ;
2022-03-14 07:19:31 +03:00
} else if ( mh . selectionsChanged . ourSelections . length > 0 ) {
2020-09-26 00:17:44 +03:00
// Some intermediate selection must have slipped in after setting the
// 'ignoreIntermediateSelections' to false. Which means we didn't count
// for it yet, but since we have selections to be ignored then we probably
// wanted this one to be ignored as well.
2023-02-16 07:22:59 +03:00
Logger . warn ( ` Ignoring slipped selection: ${ selectionsHash } ` ) ;
2020-09-26 00:17:44 +03:00
return ;
}
2020-07-25 21:45:52 +03:00
}
2020-06-30 22:58:03 +03:00
// We may receive changes from other panels when, having selections in them containing the same file
// and changing text before the selection in current panel.
if ( e . textEditor !== mh . vimState . editor ) {
return ;
}
2021-08-31 04:39:05 +03:00
if ( mh . focusChanged ) {
mh . focusChanged = false ;
2020-06-30 22:58:03 +03:00
return ;
}
if ( mh . currentMode === Mode . EasyMotionMode ) {
return ;
}
2023-09-26 05:48:47 +03:00
taskQueue . enqueueTask ( ( ) = > mh . handleSelectionChange ( e ) ) ;
2020-06-30 22:58:03 +03:00
} ,
true ,
2023-09-10 02:57:26 +03:00
false ,
2020-06-30 22:58:03 +03:00
) ;
2020-12-10 22:29:23 +03:00
registerEventListener (
context ,
vscode . window . onDidChangeTextEditorVisibleRanges ,
async ( e : vscode.TextEditorVisibleRangesChangeEvent ) = > {
2023-02-16 07:22:59 +03:00
if ( e . textEditor !== vscode . window . activeTextEditor ) {
return ;
}
2023-09-26 05:48:47 +03:00
taskQueue . enqueueTask ( async ( ) = > {
// Scrolling the viewport clears any status bar message, even errors.
const mh = await getAndUpdateModeHandler ( ) ;
if ( mh && StatusBar . lastMessageTime ) {
// TODO: Using the time elapsed works most of the time, but is a bit of a hack
const timeElapsed = Date . now ( ) - Number ( StatusBar . lastMessageTime ) ;
if ( timeElapsed > 100 ) {
StatusBar . clear ( mh . vimState , true ) ;
}
2021-06-13 02:24:53 +03:00
}
2023-09-26 05:48:47 +03:00
} ) ;
2023-09-10 02:57:26 +03:00
} ,
2020-12-10 22:29:23 +03:00
) ;
2020-06-30 22:58:03 +03:00
const compositionState = new CompositionState ( ) ;
// Override VSCode commands
2023-12-23 04:16:43 +03:00
overrideCommand ( context , 'type' , async ( args : { text : string } ) = > {
2023-09-26 05:48:47 +03:00
taskQueue . enqueueTask ( async ( ) = > {
const mh = await getAndUpdateModeHandler ( ) ;
if ( mh ) {
if ( compositionState . isInComposition ) {
compositionState . composingText += args . text ;
if ( mh . vimState . currentMode === Mode . Insert ) {
compositionState . insertedText = true ;
2023-12-23 04:16:43 +03:00
void vscode . commands . executeCommand ( 'default:type' , { text : args.text } ) ;
2023-09-26 05:48:47 +03:00
}
} else {
await mh . handleKeyEvent ( args . text ) ;
2020-11-16 22:55:44 +03:00
}
2020-06-30 22:58:03 +03:00
}
2023-09-26 05:48:47 +03:00
} ) ;
2020-06-30 22:58:03 +03:00
} ) ;
2023-12-23 04:16:43 +03:00
overrideCommand (
context ,
'replacePreviousChar' ,
async ( args : { replaceCharCnt : number ; text : string } ) = > {
taskQueue . enqueueTask ( async ( ) = > {
const mh = await getAndUpdateModeHandler ( ) ;
if ( mh ) {
if ( compositionState . isInComposition ) {
compositionState . composingText =
compositionState . composingText . substr (
0 ,
compositionState . composingText . length - args . replaceCharCnt ,
) + args . text ;
}
if ( compositionState . insertedText ) {
await vscode . commands . executeCommand ( 'default:replacePreviousChar' , {
text : args.text ,
replaceCharCnt : args.replaceCharCnt ,
} ) ;
mh . vimState . cursorStopPosition = mh . vimState . editor . selection . start ;
mh . vimState . cursorStartPosition = mh . vimState . editor . selection . start ;
}
} else {
2023-09-26 05:48:47 +03:00
await vscode . commands . executeCommand ( 'default:replacePreviousChar' , {
text : args.text ,
replaceCharCnt : args.replaceCharCnt ,
} ) ;
}
2023-12-23 04:16:43 +03:00
} ) ;
} ,
) ;
2020-06-30 22:58:03 +03:00
overrideCommand ( context , 'compositionStart' , async ( ) = > {
2023-09-26 05:48:47 +03:00
taskQueue . enqueueTask ( async ( ) = > {
compositionState . isInComposition = true ;
} ) ;
2020-06-30 22:58:03 +03:00
} ) ;
overrideCommand ( context , 'compositionEnd' , async ( ) = > {
2023-09-26 05:48:47 +03:00
taskQueue . enqueueTask ( async ( ) = > {
const mh = await getAndUpdateModeHandler ( ) ;
if ( mh ) {
if ( compositionState . insertedText ) {
mh . selectionsChanged . ignoreIntermediateSelections = true ;
await vscode . commands . executeCommand ( 'default:replacePreviousChar' , {
text : '' ,
replaceCharCnt : compositionState.composingText.length ,
} ) ;
mh . vimState . cursorStopPosition = mh . vimState . editor . selection . active ;
mh . vimState . cursorStartPosition = mh . vimState . editor . selection . active ;
mh . selectionsChanged . ignoreIntermediateSelections = false ;
}
const text = compositionState . composingText ;
await mh . handleMultipleKeyEvents ( text . split ( '' ) ) ;
2020-11-16 22:55:44 +03:00
}
2023-09-26 05:48:47 +03:00
compositionState . reset ( ) ;
} ) ;
2020-06-30 22:58:03 +03:00
} ) ;
// Register extension commands
registerCommand ( context , 'vim.showQuickpickCmdLine' , async ( ) = > {
const mh = await getAndUpdateModeHandler ( ) ;
2020-11-16 22:55:44 +03:00
if ( mh ) {
2021-10-21 06:29:15 +03:00
const cmd = await vscode . window . showInputBox ( {
prompt : 'Vim command line' ,
value : '' ,
ignoreFocusOut : false ,
valueSelection : [ 0 , 0 ] ,
} ) ;
if ( cmd ) {
await new ExCommandLine ( cmd , mh . vimState . currentMode ) . run ( mh . vimState ) ;
}
2023-12-23 04:16:43 +03:00
void mh . updateView ( ) ;
2020-11-16 22:55:44 +03:00
}
2020-06-30 22:58:03 +03:00
} ) ;
registerCommand ( context , 'vim.remap' , async ( args : ICodeKeybinding ) = > {
2023-09-26 05:48:47 +03:00
taskQueue . enqueueTask ( async ( ) = > {
const mh = await getAndUpdateModeHandler ( ) ;
if ( mh === undefined ) {
return ;
}
2020-11-16 22:55:44 +03:00
2023-09-26 05:48:47 +03:00
if ( ! args ) {
throw new Error (
"'args' is undefined. For this remap to work it needs to have 'args' with an '\"after\": string[]' and/or a '\"commands\": { command: string; args: any[] }[]'" ,
) ;
}
2020-12-07 22:05:15 +03:00
2023-09-26 05:48:47 +03:00
if ( args . after ) {
for ( const key of args . after ) {
await mh . handleKeyEvent ( Notation . NormalizeKey ( key , configuration . leader ) ) ;
}
2020-06-30 22:58:03 +03:00
}
2023-09-26 05:48:47 +03:00
if ( args . commands ) {
for ( const command of args . commands ) {
// Check if this is a vim command by looking for :
if ( command . command . startsWith ( ':' ) ) {
await new ExCommandLine (
command . command . slice ( 1 , command . command . length ) ,
mh . vimState . currentMode ,
) . run ( mh . vimState ) ;
2023-12-23 04:16:43 +03:00
void mh . updateView ( ) ;
2023-09-26 05:48:47 +03:00
} else {
2023-10-10 03:18:01 +03:00
await vscode . commands . executeCommand ( command . command , command . args ) ;
2023-09-26 05:48:47 +03:00
}
2020-06-30 22:58:03 +03:00
}
}
2023-09-26 05:48:47 +03:00
} ) ;
2020-06-30 22:58:03 +03:00
} ) ;
registerCommand ( context , 'toggleVim' , async ( ) = > {
configuration . disableExtension = ! configuration . disableExtension ;
2023-12-23 04:16:43 +03:00
void toggleExtension ( configuration . disableExtension , compositionState ) ;
2020-06-30 22:58:03 +03:00
} ) ;
for ( const boundKey of configuration . boundKeyCombinations ) {
2020-11-19 01:29:46 +03:00
const command = [ '<Esc>' , '<C-c>' ] . includes ( boundKey . key )
? async ( ) = > {
2021-06-17 02:46:51 +03:00
const mh = await getAndUpdateModeHandler ( ) ;
if ( mh && ! ( await forceStopRecursiveRemap ( mh ) ) ) {
await mh . handleKeyEvent ( ` ${ boundKey . key } ` ) ;
2020-11-19 01:29:46 +03:00
}
}
2021-06-17 02:46:51 +03:00
: async ( ) = > {
const mh = await getAndUpdateModeHandler ( ) ;
if ( mh ) {
await mh . handleKeyEvent ( ` ${ boundKey . key } ` ) ;
}
2020-11-19 01:29:46 +03:00
} ;
2023-09-26 05:48:47 +03:00
registerCommand ( context , boundKey . command , async ( ) = > {
taskQueue . enqueueTask ( command ) ;
} ) ;
2020-06-30 22:58:03 +03:00
}
2020-11-16 22:55:44 +03:00
{
// Initialize mode handler for current active Text Editor at startup.
const modeHandler = await getAndUpdateModeHandler ( ) ;
if ( modeHandler ) {
2020-11-22 01:16:46 +03:00
if ( ! configuration . startInInsertMode ) {
const vimState = modeHandler . vimState ;
// Make sure no cursors start on the EOL character (which is invalid in normal mode)
// This can happen if we quit last session in insert mode at the end of the line
vimState . cursors = vimState . cursors . map ( ( cursor ) = > {
const eolColumn = vimState . document . lineAt ( cursor . stop ) . text . length ;
if ( cursor . stop . character >= eolColumn ) {
2020-11-24 23:43:43 +03:00
const character = Math . max ( eolColumn - 1 , 0 ) ;
return cursor . withNewStop ( cursor . stop . with ( { character } ) ) ;
2020-11-22 01:16:46 +03:00
} else {
return cursor ;
}
} ) ;
}
2020-11-16 22:55:44 +03:00
// This is called last because getAndUpdateModeHandler() will change cursor
2023-12-23 04:16:43 +03:00
void modeHandler . updateView ( { drawSelection : true , revealRange : false } ) ;
2020-11-16 22:55:44 +03:00
}
2020-06-30 22:58:03 +03:00
}
// Disable automatic keyboard navigation in lists, so it doesn't interfere
// with our list navigation keybindings
2020-11-19 00:36:06 +03:00
await VSCodeContext . set ( 'listAutomaticKeyboardNavigation' , false ) ;
2020-06-30 22:58:03 +03:00
await toggleExtension ( configuration . disableExtension , compositionState ) ;
2022-12-30 05:58:08 +03:00
Logger . debug ( 'Finish.' ) ;
2020-06-30 22:58:03 +03:00
}
/ * *
* Toggles the VSCodeVim extension between Enabled mode and Disabled mode . This
* function is activated by calling the 'toggleVim' command from the Command Palette .
*
* @param isDisabled if true , sets VSCodeVim to Disabled mode ; else sets to enabled mode
* /
async function toggleExtension ( isDisabled : boolean , compositionState : CompositionState ) {
2020-11-19 00:36:06 +03:00
await VSCodeContext . set ( 'vim.active' , ! isDisabled ) ;
2020-11-16 22:55:44 +03:00
const mh = await getAndUpdateModeHandler ( ) ;
if ( mh ) {
if ( isDisabled ) {
await mh . handleKeyEvent ( SpecialKeys . ExtensionDisable ) ;
compositionState . reset ( ) ;
ModeHandlerMap . clear ( ) ;
} else {
await mh . handleKeyEvent ( SpecialKeys . ExtensionEnable ) ;
}
2020-06-30 22:58:03 +03:00
}
}
function overrideCommand (
context : vscode.ExtensionContext ,
command : string ,
2023-09-10 02:57:26 +03:00
callback : ( . . . args : any [ ] ) = > any ,
2020-06-30 22:58:03 +03:00
) {
const disposable = vscode . commands . registerCommand ( command , async ( args ) = > {
if ( configuration . disableExtension ) {
return vscode . commands . executeCommand ( 'default:' + command , args ) ;
}
if ( ! vscode . window . activeTextEditor ) {
return ;
}
if (
vscode . window . activeTextEditor . document &&
vscode . window . activeTextEditor . document . uri . toString ( ) === 'debug:input'
) {
return vscode . commands . executeCommand ( 'default:' + command , args ) ;
}
2023-12-23 04:16:43 +03:00
return callback ( args ) as vscode . Disposable ;
2020-06-30 22:58:03 +03:00
} ) ;
context . subscriptions . push ( disposable ) ;
}
2022-02-18 20:12:05 +03:00
export function registerCommand (
2020-06-30 22:58:03 +03:00
context : vscode.ExtensionContext ,
command : string ,
2020-09-21 02:44:13 +03:00
callback : ( . . . args : any [ ] ) = > any ,
2023-09-10 02:57:26 +03:00
requiresActiveEditor : boolean = true ,
2020-06-30 22:58:03 +03:00
) {
const disposable = vscode . commands . registerCommand ( command , async ( args ) = > {
2020-09-21 02:44:13 +03:00
if ( requiresActiveEditor && ! vscode . window . activeTextEditor ) {
2020-06-30 22:58:03 +03:00
return ;
}
callback ( args ) ;
} ) ;
context . subscriptions . push ( disposable ) ;
}
2022-02-18 20:12:05 +03:00
export function registerEventListener < T > (
2020-06-30 22:58:03 +03:00
context : vscode.ExtensionContext ,
event : vscode.Event < T > ,
listener : ( e : T ) = > void ,
exitOnExtensionDisable = true ,
2023-09-10 02:57:26 +03:00
exitOnTests = false ,
2020-06-30 22:58:03 +03:00
) {
const disposable = event ( async ( e ) = > {
if ( exitOnExtensionDisable && configuration . disableExtension ) {
return ;
}
if ( exitOnTests && Globals . isTesting ) {
return ;
}
listener ( e ) ;
} ) ;
context . subscriptions . push ( disposable ) ;
}
2020-11-19 01:29:46 +03:00
/ * *
* @returns true if there was a remap being executed to stop
* /
2021-06-17 02:46:51 +03:00
async function forceStopRecursiveRemap ( mh : ModeHandler ) : Promise < boolean > {
if ( mh . remapState . isCurrentlyPerformingRecursiveRemapping ) {
2020-12-08 04:57:37 +03:00
mh . remapState . forceStopRecursiveRemapping = true ;
2020-11-19 01:29:46 +03:00
return true ;
Overhaul remapping logic (#4735)
This is a pretty massive change; see pull request #4735 for full details
Most notably:
- Support for operator-pending mode, including remaps and a half-cursor decoration
- Correct handling of ambiguous remaps with timeout
- Correct handling of recursive special case when the RHS starts with the LHS
- Correct handling of multi-key remaps in insert mode
- Failed movements that occur partway through a remap stop & discard the rest of the remap
- Implement `unmap` and `mapclear` in .vimrc
Refs #463, refs #4908
Fixes #1261, fixes #1398, fixes #1579, fixes #1821, fixes #1835
Fixes #1870, fixes #1883, fixes #2041, fixes #2234, fixes #2466
Fixes #2897, fixes #2955, fixes #2975, fixes #3082, fixes #3086
Fixes #3171, fixes #3373, fixes #3413, fixes #3742, fixes #3768
Fixes #3988, fixes #4057, fixes #4118, fixes #4236, fixes #4353
Fixes #4464, fixes #4530, fixes #4532, fixes #4563, fixes #4674
Fixes #4756, fixes #4883, fixes #4928, fixes #4991, fixes #5016
Fixes #5057, fixes #5067, fixes #5084, fixes #5125
2020-08-16 21:22:51 +03:00
}
2020-11-19 01:29:46 +03:00
return false ;
Overhaul remapping logic (#4735)
This is a pretty massive change; see pull request #4735 for full details
Most notably:
- Support for operator-pending mode, including remaps and a half-cursor decoration
- Correct handling of ambiguous remaps with timeout
- Correct handling of recursive special case when the RHS starts with the LHS
- Correct handling of multi-key remaps in insert mode
- Failed movements that occur partway through a remap stop & discard the rest of the remap
- Implement `unmap` and `mapclear` in .vimrc
Refs #463, refs #4908
Fixes #1261, fixes #1398, fixes #1579, fixes #1821, fixes #1835
Fixes #1870, fixes #1883, fixes #2041, fixes #2234, fixes #2466
Fixes #2897, fixes #2955, fixes #2975, fixes #3082, fixes #3086
Fixes #3171, fixes #3373, fixes #3413, fixes #3742, fixes #3768
Fixes #3988, fixes #4057, fixes #4118, fixes #4236, fixes #4353
Fixes #4464, fixes #4530, fixes #4532, fixes #4563, fixes #4674
Fixes #4756, fixes #4883, fixes #4928, fixes #4991, fixes #5016
Fixes #5057, fixes #5067, fixes #5084, fixes #5125
2020-08-16 21:22:51 +03:00
}