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