mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-4134 adjust cursor position (#2299)
* CB-4134 adjust cursor position * CB-4134 pass controlled cursor to the editor * CB-4134 do not trim value * CB-4134 revert const history * CB-4134 review fixes * CB-4134 remove onUpdate method --------- Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
co-authored by
Evgenia Bezborodova
parent
8df248bfde
commit
3e93752d53
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { useCallback, useContext, useLayoutEffect, useRef } from 'react';
|
||||
|
||||
import { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps';
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps';
|
||||
@@ -23,6 +23,7 @@ type BaseProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChan
|
||||
description?: string;
|
||||
labelTooltip?: string;
|
||||
embedded?: boolean;
|
||||
cursorInitiallyAtEnd?: boolean;
|
||||
};
|
||||
|
||||
type ControlledProps = BaseProps & {
|
||||
@@ -54,9 +55,11 @@ export const Textarea: TextareaType = observer(function Textarea({
|
||||
description,
|
||||
labelTooltip,
|
||||
embedded,
|
||||
cursorInitiallyAtEnd,
|
||||
onChange = () => {},
|
||||
...rest
|
||||
}: ControlledProps | ObjectProps<any, any>) {
|
||||
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const layoutProps = getLayoutProps(rest);
|
||||
rest = filterLayoutFakeProps(rest);
|
||||
const styles = useS(textareaStyle);
|
||||
@@ -79,13 +82,21 @@ export const Textarea: TextareaType = observer(function Textarea({
|
||||
|
||||
const value = state ? state[name] : controlledValue;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (cursorInitiallyAtEnd && typeof value === 'string') {
|
||||
const position = value.length;
|
||||
textareaRef.current?.setSelectionRange(position, position);
|
||||
}
|
||||
}, [cursorInitiallyAtEnd]);
|
||||
|
||||
return (
|
||||
<Field {...layoutProps} className={s(styles, { field: true, embedded }, className)}>
|
||||
<FieldLabel className={s(styles, { fieldLabel: true })} title={labelTooltip || rest.title} required={required}>
|
||||
<FieldLabel className={s(styles, { fieldLabel: true })} title={labelTooltip || rest.title} required={required}>
|
||||
{children}
|
||||
</FieldLabel>
|
||||
<textarea
|
||||
{...rest}
|
||||
ref={textareaRef}
|
||||
required={required}
|
||||
className={s(styles, { textarea: true })}
|
||||
value={value ?? ''}
|
||||
|
||||
@@ -5,17 +5,25 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import type { Compartment, Extension } from '@codemirror/state';
|
||||
import type { Compartment, Extension, SelectionRange } from '@codemirror/state';
|
||||
import type { ViewUpdate } from '@codemirror/view';
|
||||
|
||||
/** Currently we support only main selection range */
|
||||
interface ISelection {
|
||||
anchor: number;
|
||||
head?: number;
|
||||
}
|
||||
|
||||
export interface IReactCodeMirrorProps extends React.PropsWithChildren {
|
||||
/** in case of using editor in editing mode its better for performance to use getValue instead */
|
||||
value?: string;
|
||||
cursor?: ISelection;
|
||||
incomingValue?: string;
|
||||
getValue?: () => string;
|
||||
extensions?: Map<Compartment, Extension>;
|
||||
readonly?: boolean;
|
||||
autoFocus?: boolean;
|
||||
onChange?: (value: string, update: ViewUpdate) => void;
|
||||
onCursorChange?: (selection: SelectionRange, update: ViewUpdate) => void;
|
||||
onUpdate?: (update: ViewUpdate) => void;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { MergeView } from '@codemirror/merge';
|
||||
import { Annotation, Compartment, Extension, StateEffect } from '@codemirror/state';
|
||||
import { Annotation, Compartment, Extension, StateEffect, TransactionSpec } from '@codemirror/state';
|
||||
import { EditorView, ViewUpdate } from '@codemirror/view';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { forwardRef, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
@@ -22,7 +22,19 @@ const External = Annotation.define<boolean>();
|
||||
|
||||
export const ReactCodemirror = observer<IReactCodeMirrorProps, IEditorRef>(
|
||||
forwardRef(function ReactCodemirror(
|
||||
{ children, getValue, value, incomingValue, extensions = new Map<Compartment, Extension>(), readonly, autoFocus, onChange, onUpdate },
|
||||
{
|
||||
children,
|
||||
getValue,
|
||||
value,
|
||||
cursor,
|
||||
incomingValue,
|
||||
extensions = new Map<Compartment, Extension>(),
|
||||
readonly,
|
||||
autoFocus,
|
||||
onChange,
|
||||
onCursorChange,
|
||||
onUpdate,
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
value = value ?? getValue?.();
|
||||
@@ -32,7 +44,7 @@ export const ReactCodemirror = observer<IReactCodeMirrorProps, IEditorRef>(
|
||||
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
||||
const [view, setView] = useState<EditorView | null>(null);
|
||||
const [incomingView, setIncomingView] = useState<EditorView | null>(null);
|
||||
const callbackRef = useObjectRef({ onChange, onUpdate });
|
||||
const callbackRef = useObjectRef({ onChange, onCursorChange, onUpdate });
|
||||
const [selection, setSelection] = useState(view?.state.selection.main ?? null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@@ -50,6 +62,11 @@ export const ReactCodemirror = observer<IReactCodeMirrorProps, IEditorRef>(
|
||||
callbackRef.onChange?.(value, update);
|
||||
}
|
||||
|
||||
if (update.selectionSet && !remote) {
|
||||
const selection = update.state.selection.main;
|
||||
callbackRef.onCursorChange?.(selection, update);
|
||||
}
|
||||
|
||||
callbackRef.onUpdate?.(update);
|
||||
});
|
||||
|
||||
@@ -136,13 +153,22 @@ export const ReactCodemirror = observer<IReactCodeMirrorProps, IEditorRef>(
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (value !== undefined && view && value !== view.state.doc.toString()) {
|
||||
view.dispatch({
|
||||
changes: { from: 0, to: view.state.doc.length, insert: value },
|
||||
annotations: [External.of(true)],
|
||||
});
|
||||
if (view) {
|
||||
const transaction: TransactionSpec = { annotations: [External.of(true)] };
|
||||
|
||||
if (value !== undefined && value !== view.state.doc.toString()) {
|
||||
transaction.changes = { from: 0, to: view.state.doc.length, insert: value };
|
||||
}
|
||||
|
||||
if (cursor && (view.state.selection.main.anchor !== cursor.anchor || view.state.selection.main.head !== cursor.head)) {
|
||||
transaction.selection = cursor;
|
||||
}
|
||||
|
||||
if (transaction.changes || transaction.selection) {
|
||||
view.dispatch(transaction);
|
||||
}
|
||||
}
|
||||
}, [value, view]);
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (incomingValue !== undefined && incomingView && incomingValue !== incomingView.state.doc.toString()) {
|
||||
|
||||
+5
-1
@@ -125,13 +125,17 @@ export const SQLCodeEditorPanel: TabContainerPanelComponent<ISqlEditorModeProps>
|
||||
<SQLCodeEditorLoader
|
||||
ref={setEditorRef}
|
||||
getValue={() => data.value}
|
||||
cursor={{
|
||||
anchor: data.cursor.begin,
|
||||
head: data.cursor.end,
|
||||
}}
|
||||
incomingValue={data.incomingValue}
|
||||
extensions={extensions}
|
||||
readonly={data.readonly}
|
||||
autoFocus
|
||||
lineNumbers
|
||||
onChange={panel.onQueryChange}
|
||||
onUpdate={panel.onUpdate}
|
||||
onCursorChange={selection => panel.onCursorChange(selection.from, selection.to)}
|
||||
>
|
||||
{data.isIncomingChanges && (
|
||||
<>
|
||||
|
||||
+5
-13
@@ -5,6 +5,7 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { action } from 'mobx';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useExecutor, useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
@@ -17,7 +18,7 @@ import type { IEditor } from '../SQLCodeEditor/useSQLCodeEditor';
|
||||
interface State {
|
||||
highlightActiveQuery: () => void;
|
||||
onQueryChange: (query: string) => void;
|
||||
onUpdate: (update: ViewUpdate) => void;
|
||||
onCursorChange: (begin: number, end?: number) => void;
|
||||
}
|
||||
|
||||
export function useSQLCodeEditorPanel(data: ISQLEditorData, editor: IEditor) {
|
||||
@@ -35,21 +36,12 @@ export function useSQLCodeEditorPanel(data: ISQLEditorData, editor: IEditor) {
|
||||
onQueryChange(query: string) {
|
||||
this.data.setScript(query);
|
||||
},
|
||||
onUpdate(update: ViewUpdate) {
|
||||
const transactions = update.transactions.filter(t => t.selection !== undefined);
|
||||
const lastTransaction = transactions[transactions.length - 1] as Transaction | undefined;
|
||||
|
||||
if (lastTransaction) {
|
||||
const from = lastTransaction.selection?.main.from ?? update.state.selection.main.from;
|
||||
const to = lastTransaction.selection?.main.to ?? update.state.selection.main.to;
|
||||
|
||||
this.data.setCursor(from, to);
|
||||
}
|
||||
onCursorChange(begin: number, end?: number) {
|
||||
this.data.setCursor(begin, end);
|
||||
},
|
||||
}),
|
||||
{},
|
||||
{ onQueryChange: action.bound, onCursorChange: action.bound },
|
||||
{ editor, data },
|
||||
['onQueryChange', 'onUpdate'],
|
||||
);
|
||||
|
||||
const updateHighlight = useCallback(
|
||||
|
||||
@@ -138,7 +138,6 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
onExecute: new SyncExecutor(),
|
||||
onSegmentExecute: new SyncExecutor(),
|
||||
onUpdate: new SyncExecutor(),
|
||||
onFormat: new SyncExecutor(),
|
||||
parser: new SQLParser(),
|
||||
|
||||
cursor: { begin: 0, end: 0 },
|
||||
@@ -225,8 +224,8 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
this.readonlyState = true;
|
||||
const formatted = await this.sqlDialectInfoService.formatScript(this.dataSource.executionContext, script.query);
|
||||
|
||||
this.onFormat.execute([script, formatted]);
|
||||
this.setScript(query.substring(0, script.begin) + formatted + query.substring(script.end));
|
||||
this.setCursor(script.begin + formatted.length);
|
||||
} finally {
|
||||
this.readonlyState = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user