Skip to content

Commit

Permalink
Vim mode
Browse files Browse the repository at this point in the history
  • Loading branch information
x0k committed Jun 6, 2024
1 parent c1e27a0 commit 16460ce
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 8 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"astro-icon": "^1.1.0",
"fast-deep-equal": "^3.1.3",
"monaco-editor": "^0.49.0",
"monaco-vim": "^0.4.1",
"pyodide": "^0.26.0",
"tailwindcss": "^3.4.4",
"typescript": "^5.4.5"
Expand Down
18 changes: 18 additions & 0 deletions src/adapters/storage.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { SyncStorage } from "@/shared";

export interface StorageState<T> {
value: T;
}

export function reactive<T>(storage: SyncStorage<T>): StorageState<T> {
let value = $state(storage.load());
return {
get value() {
return value;
},
set value(newValue) {
value = newValue;
storage.save(newValue);
},
};
}
24 changes: 17 additions & 7 deletions src/components/editor/editor-surface.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -45,29 +45,39 @@
};
});
let ed: editor.IStandaloneCodeEditor;
let ed = $state<editor.IStandaloneCodeEditor>()
let editorElement: HTMLDivElement;
$effect(() => {
model;
ed = editor.create(editorElement, {
model,
theme: "vs-dark",
fixedOverflowWidgets: true,
lineNumbers: "on",
tabSize: 2,
insertSpaces: true,
fontSize: 16,
minimap: {
enabled: false,
},
});
untrack(() => {
ed.layout({ width, height });
ed?.layout({ width, height });
})
return () => ed.dispose();
return () => {
ed?.dispose();
}
});
let panelHeight = $derived(window.innerHeight - height);
let isCollapsed = $derived(panelHeight <= MIN_PANEL_HEIGHT)
const api: SurfaceApi = {
get editor() {
return ed
},
get width() {
return width
},
Expand All @@ -89,15 +99,15 @@
return false
}
height = normalizeHeight(window.innerHeight - newHeight - PANEL_BORDER_HEIGHT)
ed.layout({ width, height }, true)
ed?.layout({ width, height }, true)
return true
},
hidePanel() {
if (isCollapsed) {
return false
}
height = normalizeHeight(window.innerHeight)
ed.layout({ width, height }, true)
ed?.layout({ width, height }, true)
return true
},
}
Expand All @@ -111,7 +121,7 @@
}}
onMove={(e) => {
width = normalizeWidth(start.x - e.clientX + start.y)
ed.layout({ width, height }, true)
ed?.layout({ width, height }, true)
}}
onMoveEnd={() => {
widthStorage.save(width)
Expand All @@ -126,7 +136,7 @@
}}
onMove={(e) => {
height = normalizeHeight(start.x - (start.y - e.clientY))
ed.layout({ width, height }, true)
ed?.layout({ width, height }, true)
}}
/>
{/snippet}
Expand Down
2 changes: 2 additions & 0 deletions src/components/editor/editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import EditorSurface from "./editor-surface.svelte";
import LangSelect from "./lang-select.svelte";
import TestingPanel from "./testing-panel.svelte";
import VimMode from './vim-mode.svelte';
interface Props<L extends Language, I, O> {
initialValue?: string;
Expand Down Expand Up @@ -75,6 +76,7 @@
children={resizer}
>
{#snippet header()}
<VimMode {api} />
<LangSelect bind:lang {languages} />
{/snippet}
</TestingPanel>
Expand Down
8 changes: 8 additions & 0 deletions src/components/editor/model.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import type { editor } from 'monaco-editor';

import { createSyncStorage } from '@/adapters/storage';
import { reactive } from '@/adapters/storage.svelte'

export interface SurfaceApi {
editor: editor.IStandaloneCodeEditor | undefined;
width: number;
panelHeight: number;
isPanelCollapsed: boolean;
showPanel(height: number): boolean;
hidePanel(): boolean;
togglePanel(height: number): boolean;
}

export const vimState = reactive(createSyncStorage(localStorage, 'editor-vim', false));
12 changes: 11 additions & 1 deletion src/components/editor/testing-panel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
type TestRunnerFactory,
} from "@/lib/testing";
import type { SurfaceApi } from './model';
import { type SurfaceApi, vimState } from './model';
import { makeTheme } from './terminal'
interface Props<I, O> {
Expand Down Expand Up @@ -189,6 +189,16 @@
class="grow pl-4 mt-4"
class:hidden={selectedTab !== Tab.Output}
></div>
<div class="grow overflow-auto" class:hidden={selectedTab !== Tab.Settings}>
<div class="flex flex-col gap-4 p-4" >
<div class="form-control">
<label class="label cursor-pointer">
<span class="label-text">Vim mode</span>
<input type="checkbox" bind:checked={vimState.value} class="checkbox" />
</label>
</div>
</div>
</div>
{@render children()}
</div>

Expand Down
25 changes: 25 additions & 0 deletions src/components/editor/vim-mode.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script lang="ts">
import { initVimMode } from "monaco-vim";
import { vimState, type SurfaceApi } from "./model";
interface Props {
api: SurfaceApi;
}
const { api }: Props = $props();
let statusElement: HTMLDivElement;
$effect(() => {
if (!api.editor || !vimState.value) {
return;
}
const mode = initVimMode(api.editor, statusElement);
return () => {
mode.dispose()
}
});
</script>

<div bind:this={statusElement}></div>
85 changes: 85 additions & 0 deletions src/monaco-vim.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copied from `typehero` project licensed under AGPL-3.0
// https://github.com/typehero/typehero/blob/main/packages/monaco/monaco-vim.d.ts

declare module 'monaco-vim' {
import type * as monaco from 'monaco-editor';

export function initVimMode(
editor: monaco.editor.IStandaloneCodeEditor,
statusbarNode?: Element | null,
): CMAdapter;

type VimModes = 'insert' | 'normal' | 'visual';

class CMAdapter {
/** removes the attached vim bindings */
dispose(): void;

attached: boolean;

editor: monaco.editor.IStandaloneCodeEditor;

statusBar: {
clear(): void;
node: HTMLElement;
};

/** @see https://codemirror.net/5/doc/manual.html#vimapi */
static Vim: {
map(lhs: string, rhs: string, ctx: VimModes): void;
unmap(lhs: string, ctx: VimModes | false): boolean;
noremap(lhs: string, rhs: string, ctx: VimModes): void;

mapCommand(
keys: string,
type: 'action',
name: string,
args?: Record<PropertyKey, unknown>,
extra?: Record<PropertyKey, unknown>,
): void;

defineAction(
name: string,
fn: (
ctx: CMAdapter,
// TODO: Document other args
...args: [unknown, unknown]
) => void,
): void;

defineEx(
name: string,
prefix: string | undefined,
fn: (
ctx: CMAdapter,
data: {
commandName: string;
input: string;
} & (
| {
argString: string;
args: [string, ...string[]];
}
| { argString?: never; args?: never }
) &
({ line: undefined } | { line: number; lineEnd: number }),
) => void,
): void;

/** clears user created mappings */
mapclear(ctx?: VimModes): void;

/** call this before `VimMode.Vim.handleKey` */
maybeInitVimState_(cma: CMAdapter): void;

/**
* calls an ex command, equivalent to `:` in vim
*
* *If it fails with `vim is null` call `VimMode.Vim.maybeInitVimState_` first*
*/
handleEx(cma: CMAdapter, ex: string): void;
};
}

export { CMAdapter as VimMode, type CMAdapter };
}

0 comments on commit 16460ce

Please sign in to comment.