11/* eslint-disable no-empty */
2- import { useEffect , useState , useRef , CSSProperties } from 'react' ;
3- import loader from '@monaco-editor/loader' ;
2+ import React , { useEffect , useState , useRef , CSSProperties } from 'react' ;
3+ import { Monaco } from '@monaco-editor/loader' ;
4+ import type { editor as oEditor } from 'monaco-editor' ;
45import { getMonaco } from './monaco' ;
56
6- type IAmbigousFn = ( ...args : any [ ] ) => any ;
7-
87// @todo fill type def for monaco editor without refering monaco editor
98/**
109 * @see https://microsoft.github.io/monaco-editor/api/index.html
1110 */
12- export interface IEditorInstance {
13- getModel : IAmbigousFn ;
14- dispose : IAmbigousFn ;
15- getValue : ( ) => string ;
16- onDidChangeModelContent : ( input : any ) => void ;
17- setTheme : ( input : string ) => void ;
18- setModelLanguage : ( model : any , language : string ) => void ;
19- layout : ( ) => void ;
20- setValue : ( value : string ) => void ;
21- executeEdits : IAmbigousFn ;
22- pushUndoStop : IAmbigousFn ;
23- EditorOption ?: Record < string , any > ;
24- getOption ?: ( input : string ) => any ;
25- onDidFocusEditorText : ( ...args : any [ ] ) => void ;
26- onDidBlurEditorText : ( ...args : any [ ] ) => void ;
27- getModifiedEditor ?: ( ) => IEditorInstance ;
28- setModel : IAmbigousFn ;
29- revealLineInCenter : IAmbigousFn ;
30- focus : IAmbigousFn ;
31- Range : new ( ...args : any [ ] ) => any ;
32- getPosition : IAmbigousFn ;
33- setPosition : IAmbigousFn ;
34- deltaDecorations : IAmbigousFn ;
35- addAction : IAmbigousFn ;
36- saveViewState : ( ) => ICodeEditorViewState ;
37- createModel : IAmbigousFn ;
38- [ key : string ] : any ;
39- }
40- export interface IMonacoInstance {
41- editor ?: {
42- create : IAmbigousFn ;
43- [ key : string ] : any ;
44- } ;
45- KeyCode ?: Record < string , any > ;
46- KeyMod ?: Record < string , any > ;
47- [ otherKeys : string ] : any ;
48- }
11+ export type IEditorInstance = oEditor . IStandaloneCodeEditor | oEditor . IStandaloneDiffEditor ;
4912
50- interface ICodeEditorViewState {
51- contributionsState : any ;
52- cursorState : any ;
53- viewState : any ;
54- }
13+ export type EditorEnhancer =
14+ ( monaco : Monaco , editorIns : IEditorInstance ) => any ;
5515
5616export interface IGeneralManacoEditorProps {
5717 /** [Monaco editor options](https://microsoft.github.io/monaco-editor/) */
5818 options ?: Record < string , any > ;
5919 /** callback after monaco's loaded and after editor's loaded */
60- editorDidMount ?: ( monaco : IMonacoInstance , editor : IEditorInstance ) => void ;
20+ editorDidMount ?: ( monaco : Monaco , editor : IEditorInstance ) => void ;
6121 /** callback after monaco's loaded and before editor's loaded */
62- editorWillMount ?: ( monaco : IMonacoInstance ) => void ;
22+ editorWillMount ?: ( monaco : Monaco ) => void ;
6323 /** path of the current model, useful when creating a multi-model editor */
6424 path ?: string ;
6525 /** whether to save the models' view states between model changes or not */
@@ -84,6 +44,7 @@ export interface IGeneralManacoEditorProps {
8444 enableOutline ?: boolean ;
8545 /** style of wrapper */
8646 style ?: CSSProperties ;
47+ enhancers ?: EditorEnhancer [ ] ;
8748}
8849
8950export interface ISingleMonacoEditorProps extends IGeneralManacoEditorProps {
@@ -98,16 +59,15 @@ export interface IDiffMonacoEditorProps extends IGeneralManacoEditorProps {
9859const CURRENT_LANGUAGE = ( ( window as any ) . locale || window . localStorage . getItem ( 'vdev-locale' ) || '' ) . replace ( / _ / , '-' ) || 'zh-CN' ;
9960export const WORD_EDITOR_INITIALIZING = CURRENT_LANGUAGE === 'en-US' ? 'Initializing Editor' : '编辑器初始化中' ;
10061
101- export const INITIAL_OPTIONS = {
62+ export const INITIAL_OPTIONS : oEditor . IStandaloneEditorConstructionOptions = {
10263 fontSize : 12 ,
10364 tabSize : 2 ,
10465 fontFamily : 'Menlo, Monaco, Courier New, monospace' ,
105- renderIndentGuides : true ,
10666 folding : true ,
10767 minimap : {
10868 enabled : false ,
10969 } ,
110- autoIndent : true ,
70+ autoIndent : 'advanced' ,
11171 contextmenu : true ,
11272 useTabStops : true ,
11373 wordBasedSuggestions : true ,
@@ -126,9 +86,34 @@ export const INITIAL_OPTIONS = {
12686 } ,
12787} ;
12888
129- export const useEditor = ( type : 'single' | 'diff' , props : IGeneralManacoEditorProps ) => {
89+ const DIFF_EDITOR_INITIAL_OPTIONS : oEditor . IStandaloneDiffEditorConstructionOptions = {
90+ fontSize : 12 ,
91+ fontFamily : 'Menlo, Monaco, Courier New, monospace' ,
92+ folding : true ,
93+ minimap : {
94+ enabled : false ,
95+ } ,
96+ autoIndent : 'advanced' ,
97+ contextmenu : true ,
98+ useTabStops : true ,
99+ formatOnPaste : true ,
100+ automaticLayout : true ,
101+ lineNumbers : 'on' ,
102+ wordWrap : 'off' ,
103+ scrollBeyondLastLine : false ,
104+ fixedOverflowWidgets : false ,
105+ snippetSuggestions : 'top' ,
106+ scrollbar : {
107+ vertical : 'auto' ,
108+ horizontal : 'auto' ,
109+ verticalScrollbarSize : 10 ,
110+ horizontalScrollbarSize : 10 ,
111+ } ,
112+ } ;
113+
114+ export const useEditor = < T = IEditorInstance > ( type : 'single' | 'diff' , props : IGeneralManacoEditorProps ) => {
130115 const {
131- editorDidMount, editorWillMount, theme, value, path, language, saveViewState, defaultValue,
116+ editorDidMount, editorWillMount, theme, value, path, language, saveViewState, defaultValue, enhancers ,
132117 } = props ;
133118
134119 const [ isEditorReady , setIsEditorReady ] = useState ( false ) ;
@@ -141,15 +126,21 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr
141126 const previousPath = usePrevious ( path ) ;
142127 const requireConfigRef = useRef ( props . requireConfig ) ;
143128 const optionRef = useRef ( props . options ) ;
144- const monacoRef = useRef < IMonacoInstance > ( ) ;
129+ const monacoRef = useRef < Monaco > ( ) ;
145130 const editorRef = useRef < IEditorInstance > ( ) ;
146131 const containerRef = useRef < HTMLDivElement > ( ) ;
147132 const typeRef = useRef ( type ) ;
148133 const editorDidMountRef = useRef < ISingleMonacoEditorProps [ 'editorDidMount' ] > ( ) ;
149134 const editorWillMountRef = useRef < ISingleMonacoEditorProps [ 'editorWillMount' ] > ( ) ;
150135
151136 const decomposeRef = useRef ( false ) ;
152- const viewStatusRef = useRef < Map < any , ICodeEditorViewState > > ( new Map ( ) ) ;
137+ const viewStatusRef = useRef < Map < any , oEditor . ICodeEditorViewState > > ( new Map ( ) ) ;
138+
139+ const enhancersRef = useRef < any > ( { } ) ;
140+
141+ useEffect ( ( ) => {
142+ enhancersRef . current . enhancers = enhancers ;
143+ } , [ enhancers ] ) ;
153144
154145 useEffect ( ( ) => {
155146 editorDidMountRef . current = editorDidMount ;
@@ -175,13 +166,8 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr
175166 // make sure loader / editor only init once
176167 useEffect ( ( ) => {
177168 setLoading ( true ) ;
178-
179- if ( requireConfigRef . current ) {
180- loader . config ( requireConfigRef . current ) ;
181- }
182-
183169 getMonaco ( requireConfigRef . current )
184- . then ( ( monaco : any ) => {
170+ . then ( ( monaco : Monaco ) => {
185171 // 兼容旧版本 monaco-editor 写死 MonacoEnvironment 的问题
186172 ( window as any ) . MonacoEnvironment = undefined ;
187173 if ( typeof ( window as any ) . define === 'function' && ( window as any ) . define . amd ) {
@@ -198,7 +184,7 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr
198184 if ( ! containerRef . current ) {
199185 return ;
200186 }
201- let editor : IEditorInstance ;
187+ let editor : oEditor . IStandaloneCodeEditor | oEditor . IStandaloneDiffEditor ;
202188 if ( typeRef . current === 'single' ) {
203189 const model = getOrCreateModel (
204190 monaco ,
@@ -223,14 +209,14 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr
223209
224210 editor = monaco . editor . createDiffEditor ( containerRef . current , {
225211 automaticLayout : true ,
226- ...INITIAL_OPTIONS ,
212+ ...DIFF_EDITOR_INITIAL_OPTIONS ,
227213 ...optionRef . current ,
228214 } ) ;
229215
230216 editor . setModel ( { original : originalModel , modified : modifiedModel } ) ;
231217 }
232-
233218 editorRef . current = editor ;
219+ enhancersRef . current . enhancers ?. forEach ( ( en : any ) => en ( monaco , editor as any ) ) ;
234220 try {
235221 editorDidMountRef . current ?.( monaco , editor ) ;
236222 } catch ( err ) { }
@@ -253,39 +239,15 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr
253239 monacoRef . current . editor . setTheme ( theme ) ;
254240 } , [ isEditorReady , theme ] ) ;
255241
256- // controlled value
257- useEffect ( ( ) => {
258- if ( ! isEditorReady ) {
259- return ;
260- }
261-
262- const editor = type === 'diff'
263- ? editorRef . current . getModifiedEditor ( )
264- : editorRef . current ;
265-
266- const nextValue = value ?? defaultValueRef . current ?? '' ;
267- if ( editor ?. getOption ?.( monacoRef . current ?. editor . EditorOption . readOnly ) ) {
268- editor ?. setValue ( nextValue ) ;
269- } else if ( value !== editor ?. getValue ( ) ) {
270- editor ?. executeEdits ( '' , [ {
271- range : editor ?. getModel ( ) . getFullModelRange ( ) ,
272- text : nextValue ,
273- forceMoveMarkers : true ,
274- } ] ) ;
275-
276- editor ?. pushUndoStop ( ) ;
277- }
278- } , [ isEditorReady , type , value ] ) ;
279-
280242 // focus status
281243 useEffect ( ( ) => {
282244 if ( ! isEditorReady ) {
283245 return ;
284246 }
285247
286248 const editor = type === 'diff'
287- ? editorRef . current . getModifiedEditor ( )
288- : editorRef . current ;
249+ ? ( editorRef . current as oEditor . IStandaloneDiffEditor ) . getModifiedEditor ( )
250+ : editorRef . current as oEditor . IStandaloneCodeEditor ;
289251 editor ?. onDidFocusEditorText ( ( ) => {
290252 ! decomposeRef . current && setFocused ( true ) ;
291253 } ) ;
@@ -301,7 +263,35 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr
301263 } ;
302264 } , [ ] ) ;
303265
304- // multi-model implementation
266+ // controlled value -- diff mode / without path only
267+ useEffect ( ( ) => {
268+ if ( ! isEditorReady ) {
269+ return ;
270+ }
271+
272+ if ( type !== 'diff' && ! ! path ) {
273+ return ;
274+ }
275+
276+ const editor = type === 'diff'
277+ ? ( editorRef . current as oEditor . IStandaloneDiffEditor ) . getModifiedEditor ( )
278+ : editorRef . current as oEditor . IStandaloneCodeEditor ;
279+
280+ const nextValue = value ?? defaultValueRef . current ?? '' ;
281+ if ( editor ?. getOption ?.( monacoRef . current ?. editor . EditorOption . readOnly ) ) {
282+ editor ?. setValue ( nextValue ) ;
283+ } else if ( value !== editor ?. getValue ( ) ) {
284+ editor ?. executeEdits ( '' , [ {
285+ range : editor ?. getModel ( ) . getFullModelRange ( ) ,
286+ text : nextValue ,
287+ forceMoveMarkers : true ,
288+ } ] ) ;
289+
290+ editor ?. pushUndoStop ( ) ;
291+ }
292+ } , [ isEditorReady , path , type , value ] ) ;
293+
294+ // multi-model && controlled value (shouldn't be diff mode)
305295 useEffect ( ( ) => {
306296 if ( ! isEditorReady ) {
307297 return ;
@@ -322,29 +312,30 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr
322312 path ,
323313 ) ;
324314
315+ const editor = editorRef . current as oEditor . IStandaloneCodeEditor ;
325316 if ( valueRef . current !== null && valueRef . current !== undefined && model . getValue ( ) !== valueRef . current ) {
326317 model . setValue ( valueRef . current ) ;
327318 }
328-
329319 if ( model !== editorRef . current . getModel ( ) ) {
330- saveViewState && viewStatusRef . current . set ( previousPath , editorRef . current . saveViewState ( ) ) ;
331- editorRef . current . setModel ( model ) ;
332- saveViewState && editorRef . current . restoreViewState ( viewStatusRef . current . get ( path ) ) ;
320+ saveViewState && viewStatusRef . current . set ( previousPath , editor . saveViewState ( ) ) ;
321+ editor . setModel ( model ) ;
322+ saveViewState && editor . restoreViewState ( viewStatusRef . current . get ( path ) ) ;
333323 }
334- } , [ isEditorReady , path , previousPath , type ] ) ;
324+ } , [ isEditorReady , value , path , previousPath , type ] ) ;
335325
326+ let retEditorRef : React . MutableRefObject < T > = editorRef as any ;
336327 return {
337328 isEditorReady,
338329 focused,
339330 loading,
340331 containerRef,
341332 monacoRef,
342- editorRef,
333+ editorRef : retEditorRef ,
343334 valueRef,
344335 } as const ;
345336} ;
346337
347- function getOrCreateModel ( monaco : IMonacoInstance , value ?: string , language ?: string , path ?: string ) {
338+ function getOrCreateModel ( monaco : Monaco , value ?: string , language ?: string , path ?: string ) {
348339 if ( path ) {
349340 const prevModel = monaco
350341 . editor
0 commit comments