Path: blob/main/extensions/copilot/test/simulation/workbench/components/editor.tsx
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import type * as monaco from 'monaco-editor';6import * as React from 'react';7import { IDiagnostic, IRange } from '../../shared/sharedTypes';8import { monacoModule } from '../utils/utils';9import { DraggableBottomBorder } from './draggableBottomBorder';10import { rangeToMonacoRange } from './monacoUtils';1112type Props = {13contents: string;14languageId: string;15lineNumbers?: boolean;16range?: IRange;17selection?: IRange;18diagnostics?: IDiagnostic[];19};2021const LINE_HEIGHT = 19;22const MAX_LINES_DUE_TO_CONTENT = 20;23const MAX_LINES_DUE_TO_RANGE = 20;24const VERTICAL_PADDING = 5;2526export const Editor = (({ contents, languageId, lineNumbers, range, selection, diagnostics }: Props) => {27if (typeof lineNumbers === 'undefined') {28lineNumbers = true;29}3031const containerRef = React.useRef<HTMLDivElement | null>(null);32const [editor, setEditor] = React.useState<monaco.editor.IStandaloneCodeEditor | null>(null);33const [altPressed, setAltPressed] = React.useState(false);3435const rangeLineCount = range ? range.end.line - range.start.line + 3 : 0;36const fileLineCount = contents.split(/\n/g).length;37const lineCount = Math.max(38Math.min(MAX_LINES_DUE_TO_RANGE, rangeLineCount),39Math.min(MAX_LINES_DUE_TO_CONTENT, fileLineCount)40);41const [height, setHeight] = React.useState<number>(LINE_HEIGHT * lineCount + 2 * VERTICAL_PADDING);4243const monaco = monacoModule.value;4445React.useEffect(() => {46if (!containerRef.current) {47return;48}49const myEditor = monaco.editor.create(containerRef.current, {50automaticLayout: true,51lineHeight: LINE_HEIGHT,52model: null,53minimap: { enabled: false },54readOnly: true,55scrollBeyondLastLine: false,56cursorBlinking: 'solid',57overviewRulerLanes: 0,58scrollbar: {59alwaysConsumeMouseWheel: true, // setting to false allows scrolling in window when scroll reaches end of the editor60},61lineNumbers: lineNumbers ? 'on' : 'off',62folding: lineNumbers ? true : false,63padding: { top: VERTICAL_PADDING, bottom: VERTICAL_PADDING }64});65setEditor(myEditor);6667return () => {68const model = myEditor.getModel();69if (model) {70model.dispose();71}72myEditor.dispose();73};74}, []);7576React.useEffect(() => {77if (editor) {78let model = editor.getModel();79if (model) {80monaco.editor.setModelLanguage(model, languageId);81model.setValue(contents);82} else {83model = monaco.editor.createModel(contents, languageId);84editor.setModel(model);85}8687if (selection) {88const mselection = rangeToMonacoRange(selection);89editor.setSelection(mselection);90editor.revealRangeInCenterIfOutsideViewport(mselection, monaco.editor.ScrollType.Immediate);91}9293if (range) {94const mrange = rangeToMonacoRange(range);9596const decorations = editor.createDecorationsCollection();97decorations.set([{98range: mrange,99options: {100className: 'step-range-highlight',101showIfCollapsed: true,102isWholeLine: true103}104}]);105editor.revealRangeInCenter(mrange, monaco.editor.ScrollType.Immediate);106}107108if (diagnostics && diagnostics.length > 0) {109editor.createDecorationsCollection().set(createDiagnosticDecorations(diagnostics, model));110}111}112}, [editor, contents, languageId, lineNumbers, range, selection, diagnostics]);113114React.useEffect(() => {115const handleKeyDown = (e: KeyboardEvent) => {116if (e.altKey) {117setAltPressed(true);118}119};120121const handleKeyUp = (e: KeyboardEvent) => {122if (!e.altKey) {123setAltPressed(false);124}125};126127window.addEventListener('keydown', handleKeyDown);128window.addEventListener('keyup', handleKeyUp);129return () => {130window.removeEventListener('keydown', handleKeyDown);131window.removeEventListener('keyup', handleKeyUp);132};133}, []);134135const handleWheel = (e: React.WheelEvent) => {136if (altPressed) {137e.preventDefault();138e.stopPropagation();139}140};141142return (143<div>144<div className='file-editor-container' style={{ height: `${height}px`, position: 'relative' }} ref={containerRef}>145<div146className='overlay'147style={{148position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,149pointerEvents: altPressed ? 'auto' : 'none',150backgroundColor: 'transparent',151zIndex: 1000152}}153onWheel={handleWheel}154/>155</div>156<DraggableBottomBorder height={height} setHeight={setHeight} />157</div>158);159});160161export function createDiagnosticDecorations(diagnostics: IDiagnostic[], model: monaco.editor.ITextModel): monaco.editor.IModelDeltaDecoration[] {162const monaco = monacoModule.value;163const decs: monaco.editor.IModelDeltaDecoration[] = [];164for (const diagnostic of diagnostics) {165const mrange = rangeToMonacoRange(diagnostic.range);166const validRange = isValidRange(mrange, model);167decs.push({168range: mrange,169options: {170className: validRange ? 'dec-diagnostic' : 'dec-diagnostic-invalid-range',171overviewRuler: { color: '#000000', position: monaco.editor.OverviewRulerLane.Full },172showIfCollapsed: true,173hoverMessage: [174{ value: `${validRange ? 'Range' : 'Invalid range'}: (${diagnostic.range.start.line},${diagnostic.range.start.character} - ${diagnostic.range.end.line},${diagnostic.range.end.character})` },175{ value: diagnostic.message }],176}177});178}179return decs;180}181182function isValidRange(range: monaco.Range, model: monaco.editor.ITextModel) {183if (!model.validateRange(range).equalsRange(range)) {184return false;185}186const positionInsideWord = (pos: monaco.Position) => {187const word = model.getWordAtPosition(pos);188return word && word.startColumn < pos.column && word.endColumn > pos.column;189};190return !positionInsideWord(range.getStartPosition()) && !positionInsideWord(range.getEndPosition());191}192193194