Skip to content

Commit

Permalink
feat(mode): switch to ModeChanged for more detailed mode information (
Browse files Browse the repository at this point in the history
#1255)

* feat(mode): switch to `ModeChanged` for more detailed mode information
This gives us information for when the mode switches between kinds of visual modes

* fix: update mode on init

* fix: try BufEnter

* fix: rename full to raw

* fact: rename single to char

* fact: rename long to name

* fact: rename raw to shortname
  • Loading branch information
theol0403 committed Jul 4, 2023
1 parent 88103a8 commit 97140a7
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 31 deletions.
10 changes: 7 additions & 3 deletions src/cursor_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export class CursorManager
}

private onDidChangeVisibleTextEditors = (): void => {
this.updateCursorStyle(this.main.modeManager.currentMode);
this.updateCursorStyle(this.main.modeManager.currentMode.name);
};

private onSelectionChanged = async (e: TextEditorSelectionChangeEvent): Promise<void> => {
Expand Down Expand Up @@ -486,10 +486,14 @@ export class CursorManager
window.activeTextEditor.selections = newSelections;
}

private onModeChange = (newMode: string): void => {
private onModeChange = (): void => {
if (this.main.modeManager.isInsertMode) this.wantInsertCursorUpdate = true;

if (newMode === "normal" && window.activeTextEditor && window.activeTextEditor.selections.length > 1) {
if (
this.main.modeManager.isNormalMode &&
window.activeTextEditor &&
window.activeTextEditor.selections.length > 1
) {
window.activeTextEditor.selections = [
new Selection(window.activeTextEditor.selection.active, window.activeTextEditor.selection.active),
];
Expand Down
1 change: 0 additions & 1 deletion src/main_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,6 @@ export class MainController implements vscode.Disposable {
private onNeovimNotification = (method: string, events: [string, ...any[]]): void => {
// order matters here, modeManager should be processed first
const redrawManagers: NeovimRedrawProcessable[] = [
this.modeManager,
this.bufferManager,
this.viewportManager,
this.cursorManager,
Expand Down
84 changes: 57 additions & 27 deletions src/mode_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,45 @@ import { EventEmitter } from "events";
import { commands, Disposable } from "vscode";

import { Logger } from "./logger";
import { NeovimExtensionRequestProcessable, NeovimRedrawProcessable } from "./neovim_events_processable";
import { findLastEvent } from "./utils";
import { NeovimExtensionRequestProcessable } from "./neovim_events_processable";

const LOG_PREFIX = "ModeManager";

export class ModeManager implements Disposable, NeovimRedrawProcessable, NeovimExtensionRequestProcessable {
// a representation of the current mode. can be read in different ways using accessors. underlying type is shortname name as returned by `:help mode()`
class Mode {
public constructor(public shortname: string = "") {}
// mode 1-char code: n, v, V, i, s, ...
// converts ^v into v
public get char(): string {
return this.shortname.charCodeAt(0) == 22 ? "v" : this.shortname.charAt(0);
}
// mode long name
public get name(): "insert" | "visual" | "normal" {
switch (this.char.toLowerCase()) {
case "i":
return "insert";
case "v":
return "visual";
case "n":
default:
return "normal";
}
}
// visual mode name
public get visual(): "char" | "line" | "block" {
return this.char === "V" ? "line" : this.shortname.charAt(0) === "v" ? "char" : "block";
}
}
export class ModeManager implements Disposable, NeovimExtensionRequestProcessable {
private disposables: Disposable[] = [];
/**
* Current neovim mode
*/
private mode = "";
private mode: Mode = new Mode();
/**
* Last neovim mode
*/
private last: Mode = new Mode();
/**
* True when macro recording in insert mode
*/
Expand All @@ -26,53 +54,55 @@ export class ModeManager implements Disposable, NeovimRedrawProcessable, NeovimE
this.disposables.forEach((d) => d.dispose());
}

public get currentMode(): string {
public get currentMode(): Mode {
return this.mode;
}

public get lastMode(): Mode {
return this.last;
}

public get isInsertMode(): boolean {
return this.mode === "insert";
return this.mode.name === "insert";
}

public get isVisualMode(): boolean {
return this.mode === "visual";
return this.mode.name === "visual";
}

public get isNormalMode(): boolean {
return this.mode === "normal";
return this.mode.name === "normal";
}

public get isRecordingInInsertMode(): boolean {
return this.isRecording;
}

public onModeChange(callback: (newMode: string) => void): void {
public onModeChange(callback: () => void): void {
this.eventEmitter.on("neovimModeChanged", callback);
}

public handleRedrawBatch(batch: [string, ...unknown[]][]): void {
const lastModeChange = findLastEvent("mode_change", batch);
if (lastModeChange) {
const modeArg = lastModeChange[1] as [string, never] | undefined;
if (modeArg && modeArg[0] && modeArg[0] !== this.mode) {
const modeName = modeArg[0];
this.logger.debug(`${LOG_PREFIX}: Changing mode to ${modeName}`);
this.mode = modeName;
public async handleExtensionRequest(name: string, args: unknown[]): Promise<void> {
switch (name) {
case "mode-changed": {
const [oldMode, newMode] = args as [string, string];
this.logger.debug(`${LOG_PREFIX}: Changing mode from ${oldMode} to ${newMode}`);
this.mode = new Mode(newMode);
this.last = new Mode(oldMode);
if (!this.isInsertMode && this.isRecording) {
this.isRecording = false;
commands.executeCommand("setContext", "neovim.recording", false);
}
commands.executeCommand("setContext", "neovim.mode", this.mode);
this.eventEmitter.emit("neovimModeChanged", modeName);
commands.executeCommand("setContext", "neovim.mode", this.mode.name);
this.eventEmitter.emit("neovimModeChanged");
break;
}
case "notify-recording": {
this.logger.debug(`${LOG_PREFIX}: setting recording flag`);
this.isRecording = true;
commands.executeCommand("setContext", "neovim.recording", true);
break;
}
}
}

public async handleExtensionRequest(name: string): Promise<void> {
if (name === "notify-recording") {
this.logger.debug(`${LOG_PREFIX}: setting recording flag`);
this.isRecording = true;
commands.executeCommand("setContext", "neovim.recording", true);
}
}
}
1 change: 1 addition & 0 deletions vim/vscode-neovim.vim
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ augroup VscodeGeneral
" Looks like external windows are coming with "set wrap" set automatically, disable them
" autocmd WinNew,WinEnter * :set nowrap
autocmd WinScrolled * call VSCodeExtensionNotify('window-scroll', win_getid(), winsaveview())
autocmd ModeChanged * call VSCodeExtensionNotify('mode-changed', v:event.old_mode, v:event.new_mode)
augroup END


Expand Down

0 comments on commit 97140a7

Please sign in to comment.