mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-28 20:21:52 +01:00
Vim/emacs/vscode keybindings
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useFolders } from '../hooks/useFolders';
|
||||
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { Input } from './core/Input';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { MarkdownEditor } from './MarkdownEditor';
|
||||
|
||||
@@ -17,13 +17,14 @@ export function FolderSettingsDialog({ folderId }: Props) {
|
||||
|
||||
return (
|
||||
<VStack space={3} className="pb-3">
|
||||
<PlainInput
|
||||
<Input
|
||||
label="Folder Name"
|
||||
defaultValue={folder.name}
|
||||
onChange={(name) => {
|
||||
if (folderId == null) return;
|
||||
updateFolder({ id: folderId, update: (folder) => ({ ...folder, name }) });
|
||||
}}
|
||||
stateKey={`name.${folder.id}`}
|
||||
/>
|
||||
|
||||
<MarkdownEditor
|
||||
|
||||
@@ -45,8 +45,8 @@ import type {
|
||||
GenericCompletionOption,
|
||||
} from './core/Editor/genericCompletion';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { Input } from './core/Input';
|
||||
import type { Pair } from './core/PairEditor';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import { EmptyStateText } from './EmptyStateText';
|
||||
@@ -481,7 +481,7 @@ export const RequestPane = memo(function RequestPane({
|
||||
</TabContent>
|
||||
<TabContent value={TAB_DESCRIPTION}>
|
||||
<div className="grid grid-rows-[auto_minmax(0,1fr)] h-full">
|
||||
<PlainInput
|
||||
<Input
|
||||
label="Request Name"
|
||||
hideLabel
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
@@ -490,6 +490,7 @@ export const RequestPane = memo(function RequestPane({
|
||||
containerClassName="border-0"
|
||||
placeholder="Request Name"
|
||||
onChange={(name) => updateRequest({ id: activeRequestId, update: { name } })}
|
||||
stateKey={`name.${activeRequest.id}`}
|
||||
/>
|
||||
<MarkdownEditor
|
||||
name="request-description"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { EditorKeymap } from '@yaakapp-internal/models';
|
||||
import React from 'react';
|
||||
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
||||
import { useResolvedAppearance } from '../../hooks/useResolvedAppearance';
|
||||
@@ -9,7 +10,7 @@ import { getThemes } from '../../lib/theme/themes';
|
||||
import { isThemeDark } from '../../lib/theme/window';
|
||||
import type { ButtonProps } from '../core/Button';
|
||||
import { Checkbox } from '../core/Checkbox';
|
||||
import {Editor} from "../core/Editor/Editor";
|
||||
import { Editor } from '../core/Editor/Editor';
|
||||
import type { IconProps } from '../core/Icon';
|
||||
import { Icon } from '../core/Icon';
|
||||
import { IconButton } from '../core/IconButton';
|
||||
@@ -18,10 +19,13 @@ import { Select } from '../core/Select';
|
||||
import { Separator } from '../core/Separator';
|
||||
import { HStack, VStack } from '../core/Stacks';
|
||||
|
||||
const fontSizes = [
|
||||
const fontSizeOptions = [
|
||||
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
|
||||
].map((n) => ({ label: `${n}`, value: `${n}` }));
|
||||
|
||||
const keymaps: EditorKeymap[] = ['default', 'vim', 'vscode', 'emacs'];
|
||||
const keymapOptions = keymaps.map((n) => ({ label: n, value: n }));
|
||||
|
||||
const buttonColors: ButtonProps['color'][] = [
|
||||
'primary',
|
||||
'info',
|
||||
@@ -86,9 +90,9 @@ export function SettingsAppearance() {
|
||||
label="Font Size"
|
||||
labelPosition="left"
|
||||
value={`${settings.interfaceFontSize}`}
|
||||
options={fontSizes}
|
||||
options={fontSizeOptions}
|
||||
onChange={(v) => updateSettings.mutate({ interfaceFontSize: parseInt(v) })}
|
||||
event="font-size.interface"
|
||||
event="ui-font-size"
|
||||
/>
|
||||
<Select
|
||||
size="sm"
|
||||
@@ -96,15 +100,25 @@ export function SettingsAppearance() {
|
||||
label="Editor Font Size"
|
||||
labelPosition="left"
|
||||
value={`${settings.editorFontSize}`}
|
||||
options={fontSizes}
|
||||
options={fontSizeOptions}
|
||||
onChange={(v) => updateSettings.mutate({ editorFontSize: clamp(parseInt(v) || 14, 8, 30) })}
|
||||
event="font-size.editor"
|
||||
event="editor-font-size"
|
||||
/>
|
||||
<Checkbox
|
||||
checked={settings.editorSoftWrap}
|
||||
title="Wrap Editor Lines"
|
||||
onChange={(editorSoftWrap) => updateSettings.mutate({ editorSoftWrap })}
|
||||
event="wrap-lines"
|
||||
event="editor-wrap-lines"
|
||||
/>
|
||||
<Select
|
||||
size="sm"
|
||||
name="editorKeymap"
|
||||
label="Editor Key Map"
|
||||
labelPosition="left"
|
||||
value={`${settings.editorKeymap}`}
|
||||
options={keymapOptions}
|
||||
onChange={(v) => updateSettings.mutate({ editorKeymap: v })}
|
||||
event="editor-keymap"
|
||||
/>
|
||||
|
||||
<Separator className="my-4" />
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useDeleteActiveWorkspace } from '../hooks/useDeleteActiveWorkspace';
|
||||
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { Button } from './core/Button';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { Input } from './core/Input';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { MarkdownEditor } from './MarkdownEditor';
|
||||
import { SelectFile } from './SelectFile';
|
||||
@@ -22,10 +22,11 @@ export function WorkspaceSettingsDialog({ workspaceId, hide }: Props) {
|
||||
|
||||
return (
|
||||
<VStack space={3} alignItems="start" className="pb-3 max-h-[50vh]">
|
||||
<PlainInput
|
||||
<Input
|
||||
label="Workspace Name"
|
||||
defaultValue={workspace.name}
|
||||
onChange={(name) => updateWorkspace({ name })}
|
||||
stateKey={`name.${workspace.id}`}
|
||||
/>
|
||||
|
||||
<MarkdownEditor
|
||||
|
||||
@@ -4,16 +4,28 @@
|
||||
.cm-editor {
|
||||
@apply w-full block text-base;
|
||||
|
||||
/* Regular cursor */
|
||||
.cm-cursor {
|
||||
@apply border-text !important;
|
||||
/* Widen the cursor */
|
||||
/* Widen the cursor a bit */
|
||||
@apply border-l-[2px];
|
||||
}
|
||||
|
||||
/* Vim-mode cursor */
|
||||
.cm-fat-cursor {
|
||||
@apply bg-text opacity-60;
|
||||
}
|
||||
|
||||
&.cm-focused {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
&:not(.cm-focused) {
|
||||
.cm-cursor, .cm-fat-cursor {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-content {
|
||||
@apply py-0;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ import { foldState, forceParsing } from '@codemirror/language';
|
||||
import type { EditorStateConfig, Extension } from '@codemirror/state';
|
||||
import { Compartment, EditorState } from '@codemirror/state';
|
||||
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
|
||||
import type { EnvironmentVariable } from '@yaakapp-internal/models';
|
||||
import { emacs } from '@replit/codemirror-emacs';
|
||||
import { vim } from '@replit/codemirror-vim';
|
||||
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
||||
import type { EditorKeymap, EnvironmentVariable } from '@yaakapp-internal/models';
|
||||
import type { TemplateFunction } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import { EditorView } from 'codemirror';
|
||||
@@ -33,15 +36,17 @@ import { TemplateVariableDialog } from '../../TemplateVariableDialog';
|
||||
import { IconButton } from '../IconButton';
|
||||
import { HStack } from '../Stacks';
|
||||
import './Editor.css';
|
||||
import {
|
||||
baseExtensions,
|
||||
emptyExtension,
|
||||
getLanguageExtension,
|
||||
multiLineExtensions,
|
||||
} from './extensions';
|
||||
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
|
||||
import type { GenericCompletionConfig } from './genericCompletion';
|
||||
import { singleLineExtensions } from './singleLine';
|
||||
|
||||
const keymapExtensions: Record<EditorKeymap, Extension> = {
|
||||
vim: vim(),
|
||||
emacs: emacs(),
|
||||
vscode: keymap.of(vscodeKeymap),
|
||||
default: [],
|
||||
};
|
||||
|
||||
export interface EditorProps {
|
||||
id?: string;
|
||||
readOnly?: boolean;
|
||||
@@ -86,6 +91,7 @@ export interface EditorProps {
|
||||
const stateFields = { history: historyField, folds: foldState };
|
||||
|
||||
const emptyVariables: EnvironmentVariable[] = [];
|
||||
const emptyExtension: Extension = [];
|
||||
|
||||
export const Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
|
||||
{
|
||||
@@ -119,6 +125,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
ref,
|
||||
) {
|
||||
const settings = useSettings();
|
||||
|
||||
const templateFunctions = useTemplateFunctions();
|
||||
const allEnvironmentVariables = useActiveEnvironmentVariables();
|
||||
const environmentVariables = autocompleteVariables ? allEnvironmentVariables : emptyVariables;
|
||||
@@ -178,6 +185,25 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
[placeholder],
|
||||
);
|
||||
|
||||
// Update vim
|
||||
const keymapCompartment = useRef(new Compartment());
|
||||
useEffect(
|
||||
function configureKeymap() {
|
||||
if (cm.current === null) return;
|
||||
const current = keymapCompartment.current.get(cm.current.view.state) ?? [];
|
||||
// PERF: This is expensive with hundreds of editors on screen, so only do it when necessary
|
||||
if (settings.editorKeymap === 'default' && current === keymapExtensions['default']) return; // Nothing to do
|
||||
if (settings.editorKeymap === 'vim' && current === keymapExtensions['vim']) return; // Nothing to do
|
||||
if (settings.editorKeymap === 'vscode' && current === keymapExtensions['vscode']) return; // Nothing to do
|
||||
if (settings.editorKeymap === 'emacs' && current === keymapExtensions['emacs']) return; // Nothing to do
|
||||
|
||||
const ext = keymapExtensions[settings.editorKeymap] ?? keymapExtensions['default'];
|
||||
const effect = keymapCompartment.current.reconfigure(ext);
|
||||
cm.current.view.dispatch({ effects: effect });
|
||||
},
|
||||
[settings.editorKeymap],
|
||||
);
|
||||
|
||||
// Update wrap lines
|
||||
const wrapLinesCompartment = useRef(new Compartment());
|
||||
useEffect(
|
||||
@@ -188,7 +214,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
if (wrapLines && current !== emptyExtension) return; // Nothing to do
|
||||
if (!wrapLines && current === emptyExtension) return; // Nothing to do
|
||||
|
||||
const ext = wrapLines ? EditorView.lineWrapping : emptyExtension;
|
||||
const ext = wrapLines ? EditorView.lineWrapping : [];
|
||||
const effect = wrapLinesCompartment.current.reconfigure(ext);
|
||||
cm.current?.view.dispatch({ effects: effect });
|
||||
},
|
||||
@@ -331,7 +357,10 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
placeholderCompartment.current.of(
|
||||
placeholderExt(placeholderElFromText(placeholder ?? '')),
|
||||
),
|
||||
wrapLinesCompartment.current.of(wrapLines ? EditorView.lineWrapping : emptyExtension),
|
||||
wrapLinesCompartment.current.of(wrapLines ? EditorView.lineWrapping : []),
|
||||
keymapCompartment.current.of(
|
||||
keymapExtensions[settings.editorKeymap] ?? keymapExtensions['default'],
|
||||
),
|
||||
...getExtensions({
|
||||
container,
|
||||
readOnly,
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
import { lintKeymap } from '@codemirror/lint';
|
||||
|
||||
import { searchKeymap } from '@codemirror/search';
|
||||
import type { Extension } from '@codemirror/state';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import {
|
||||
crosshairCursor,
|
||||
@@ -86,8 +85,6 @@ const syntaxExtensions: Record<NonNullable<EditorProps['language']>, LanguageSup
|
||||
markdown: markdown(),
|
||||
};
|
||||
|
||||
export const emptyExtension: Extension = [];
|
||||
|
||||
export function getLanguageExtension({
|
||||
language,
|
||||
useTemplating = false,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { PromptTextRequest } from '@yaakapp-internal/plugins';
|
||||
import type { FormEvent, ReactNode } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import {PlainInput} from "./PlainInput";
|
||||
import { HStack } from './Stacks';
|
||||
import { Button } from './Button';
|
||||
import { Input } from './Input';
|
||||
import { HStack } from './Stacks';
|
||||
|
||||
export type PromptProps = Omit<PromptTextRequest, 'id' | 'title' | 'description'> & {
|
||||
description?: ReactNode;
|
||||
@@ -35,7 +35,7 @@ export function Prompt({
|
||||
className="grid grid-rows-[auto_auto] grid-cols-[minmax(0,1fr)] gap-4 mb-4"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<PlainInput
|
||||
<Input
|
||||
hideLabel
|
||||
autoSelect
|
||||
require={require}
|
||||
@@ -43,6 +43,7 @@ export function Prompt({
|
||||
label={label}
|
||||
defaultValue={defaultValue}
|
||||
onChange={setValue}
|
||||
stateKey={null}
|
||||
/>
|
||||
<HStack space={2} justifyContent="end">
|
||||
<Button onClick={onCancel} variant="border" color="secondary">
|
||||
|
||||
Reference in New Issue
Block a user