import * as React from 'react'; import { FunctionComponent, useContext, useRef, useCallback, useMemo, useEffect, useState, CSSProperties, } from 'react'; import { useOnClickOutside, useRefEffect } from '../../hooks'; import * as editorCommon from '../../vs/editor/common/editorCommon'; import { EditorViewContext } from '../../contexts/EditorViewContext'; import './TextAreaInput.css'; import { Position } from '../../vs/editor/common/core/position'; import { px } from '../../utilities'; import { FontInfoContext } from '../../contexts/FontInfoContext'; import { WritingMode } from '../../config/writingMode'; import { LayoutInfoContext } from '../../contexts/LayoutInfoContext'; type Props = { offset: number; wordWrap?: boolean; }; export interface ITypeData { text: string; replacePrevCharCnt: number; replaceNextCharCnt: number; positionDelta: number; } export class TextAreaState { public static readonly EMPTY = new TextAreaState('', 0, 0, null, null); public readonly value: string; public readonly selectionStart: number; public readonly selectionEnd: number; public readonly selectionStartPosition: Position | null; public readonly selectionEndPosition: Position | null; constructor( value: string, selectionStart: number, selectionEnd: number, selectionStartPosition: Position | null, selectionEndPosition: Position | null ) { this.value = value; this.selectionStart = selectionStart; this.selectionEnd = selectionEnd; this.selectionStartPosition = selectionStartPosition; this.selectionEndPosition = selectionEndPosition; } public static selectedText(text: string): TextAreaState { return new TextAreaState(text, 0, text.length, null, null); } } export const TextAreaInput: FunctionComponent = (props) => { const { offset, wordWrap = false } = props; const { editor, renderingContext: ctx, writingMode, heightKey, } = useContext(EditorViewContext); const { layoutInfo } = useContext(LayoutInfoContext); const { fontInfo } = useContext(FontInfoContext); const textAreaRef = useRefEffect((textArea) => { textArea.focus(); }); const [textAreaState, setTextAreaState] = useState(TextAreaState.EMPTY); // const [state, setState] = useState({ // text: '', // replacePrevCharCnt: 0, // replaceNextCharCnt: 0, // positionDelta: 0, // }); const [isDoingComposition, setIsDoingComposition] = useState(false); const [left, setLeft] = useState(0); const [top, setTop] = useState(0); const [startPosition, setStartPosition] = useState(); const [endPosition, setEndPosition] = useState(); const [content, setContent] = useState(''); // useOnClickOutside(textAreaRef, () => { // if (textAreaRef.current) { // textAreaRef.current.value = ''; // } // setIsDoingComposition(false); // }); useEffect(() => { editor.onDidFocusEditorText(() => { console.log('Focus!'); textAreaRef.current?.focus(); }); }, [editor, textAreaRef]); const handler: Record void> = useMemo(() => { return { Backspace: () => { editor.trigger(null, 'deleteLeft', null); }, Enter: () => { editor.trigger('keyboard', editorCommon.Handler.Type, { text: '\n' }); }, Alt: () => { // editor.trigger('keyboard', editorCommon.Handler.Type, { text: '\n' }); }, ArrowLeft: () => { editor.trigger('keyboard', 'cursorLeft', {}); }, ArrowRight: () => { editor.trigger( 'keyboard', editorCommon.Handler.CompositionEnd, undefined ); editor.trigger('keyboard', 'cursorRight', {}); }, ArrowUp: () => { editor.trigger( 'keyboard', editorCommon.Handler.CompositionEnd, undefined ); editor.trigger('keyboard', 'cursorUp', {}); }, ArrowDown: () => { editor.trigger( 'keyboard', editorCommon.Handler.CompositionEnd, undefined ); editor.trigger('keyboard', 'cursorDown', {}); }, KanjiMode: () => { // console.log('CompositionStart'); // editor.trigger('', editorCommon.Handler.CompositionStart, undefined); // setIsDoingComposition(true); }, Control: () => {}, }; }, [editor]); const onKeyDown = useCallback( (key: string) => { if (key === 'Process') { return; } if (handler[key]) { handler[key](); return; } if (isDoingComposition) { return; } editor.trigger('keyboard', editorCommon.Handler.Type, { text: key, }); if (textAreaRef.current) { textAreaRef.current.value = ''; } }, [editor, isDoingComposition, handler, textAreaRef] ); /** * Deduce the composition input from a string. */ const deduceComposition = useCallback( (text: string): [TextAreaState, ITypeData] => { const oldState = textAreaState; const newState = TextAreaState.selectedText(text); const typeInput: ITypeData = { text: newState.value, replacePrevCharCnt: oldState.selectionEnd - oldState.selectionStart, replaceNextCharCnt: 0, positionDelta: 0, }; return [newState, typeInput]; }, [textAreaState] ); useEffect(() => { if (isDoingComposition) { return; } setLeft(offset); }, [offset, isDoingComposition]); const containerStyle: CSSProperties = useMemo(() => { if (!ctx) { return {}; } // switch (writingMode) { // case WritingMode.LeftToRightHorizontalWriting: // default: // break; // } if (writingMode === WritingMode.RightToLeftVerticalWriting) { return { top: px(ctx.scrollLeft), width: '1000px', lineHeight: px(fontInfo.lineHeight), height: px(ctx?.viewportWidth! - fontInfo.fontSize), }; } return { left: px(ctx.scrollLeft), height: px(1000), lineHeight: px(fontInfo.lineHeight), width: px(layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth), }; }, [ctx, fontInfo, top, writingMode, layoutInfo]); const inputStyle: CSSProperties = useMemo(() => { switch (writingMode) { case WritingMode.RightToLeftVerticalWriting: return { height: '100%', width: '1000px', textIndent: px(left - ctx!.scrollLeft), }; default: break; } return { width: '100%', height: '1000px', textIndent: px(left - ctx!.scrollLeft), }; }, [left, ctx]); const textIndentStyle: CSSProperties = useMemo(() => { switch (writingMode) { case WritingMode.RightToLeftVerticalWriting: return { height: px(left - ctx!.scrollLeft) }; default: break; } return { width: px(left - ctx!.scrollLeft) }; }, [left, ctx, writingMode]); return (
{content}