1
1
import { nls } from '@theia/core/lib/common/nls' ;
2
- import { injectable } from '@theia/core/shared/inversify' ;
2
+ import { inject , injectable } from '@theia/core/shared/inversify' ;
3
3
import type { EditorOpenerOptions } from '@theia/editor/lib/browser/editor-manager' ;
4
4
import { Later } from '../../common/nls' ;
5
5
import { SketchesError } from '../../common/protocol' ;
@@ -10,9 +10,19 @@ import {
10
10
URI ,
11
11
} from './contribution' ;
12
12
import { SaveAsSketch } from './save-as-sketch' ;
13
+ import { promptMoveSketch } from './open-sketch' ;
14
+ import { ApplicationError } from '@theia/core/lib/common/application-error' ;
15
+ import { Deferred , wait } from '@theia/core/lib/common/promise-util' ;
16
+ import { EditorWidget } from '@theia/editor/lib/browser/editor-widget' ;
17
+ import { DisposableCollection } from '@theia/core/lib/common/disposable' ;
18
+ import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor' ;
19
+ import { ContextKeyService as VSCodeContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/browser/contextKeyService' ;
13
20
14
21
@injectable ( )
15
22
export class OpenSketchFiles extends SketchContribution {
23
+ @inject ( VSCodeContextKeyService )
24
+ private readonly contextKeyService : VSCodeContextKeyService ;
25
+
16
26
override registerCommands ( registry : CommandRegistry ) : void {
17
27
registry . registerCommand ( OpenSketchFiles . Commands . OPEN_SKETCH_FILES , {
18
28
execute : ( uri : URI ) => this . openSketchFiles ( uri ) ,
@@ -55,9 +65,17 @@ export class OpenSketchFiles extends SketchContribution {
55
65
}
56
66
} ) ;
57
67
}
68
+ const { workspaceError } = this . workspaceService ;
69
+ if ( SketchesError . InvalidName . is ( workspaceError ) ) {
70
+ return this . promptMove ( workspaceError ) ;
71
+ }
58
72
} catch ( err ) {
73
+ if ( SketchesError . InvalidName . is ( err ) ) {
74
+ return this . promptMove ( err ) ;
75
+ }
76
+
59
77
if ( SketchesError . NotFound . is ( err ) ) {
60
- this . openFallbackSketch ( ) ;
78
+ return this . openFallbackSketch ( ) ;
61
79
} else {
62
80
console . error ( err ) ;
63
81
const message =
@@ -71,6 +89,29 @@ export class OpenSketchFiles extends SketchContribution {
71
89
}
72
90
}
73
91
92
+ private async promptMove (
93
+ err : ApplicationError <
94
+ number ,
95
+ {
96
+ invalidMainSketchUri : string ;
97
+ }
98
+ >
99
+ ) : Promise < void > {
100
+ const { invalidMainSketchUri } = err . data ;
101
+ requestAnimationFrame ( ( ) => this . messageService . error ( err . message ) ) ;
102
+ await wait ( 10 ) ; // let IDE2 toast the error message.
103
+ const movedSketch = await promptMoveSketch ( invalidMainSketchUri , {
104
+ fileService : this . fileService ,
105
+ sketchService : this . sketchService ,
106
+ labelProvider : this . labelProvider ,
107
+ } ) ;
108
+ if ( movedSketch ) {
109
+ return this . workspaceService . open ( new URI ( movedSketch . uri ) , {
110
+ preserveWindow : true ,
111
+ } ) ;
112
+ }
113
+ }
114
+
74
115
private async openFallbackSketch ( ) : Promise < void > {
75
116
const sketch = await this . sketchService . createNewSketch ( ) ;
76
117
this . workspaceService . open ( new URI ( sketch . uri ) , { preserveWindow : true } ) ;
@@ -84,15 +125,69 @@ export class OpenSketchFiles extends SketchContribution {
84
125
const widget = this . editorManager . all . find (
85
126
( widget ) => widget . editor . uri . toString ( ) === uri
86
127
) ;
128
+ const disposables = new DisposableCollection ( ) ;
87
129
if ( ! widget || forceOpen ) {
88
- return this . editorManager . open (
130
+ const deferred = new Deferred < EditorWidget > ( ) ;
131
+ disposables . push (
132
+ this . editorManager . onCreated ( ( editor ) => {
133
+ if ( editor . editor . uri . toString ( ) === uri ) {
134
+ if ( editor . isVisible ) {
135
+ disposables . dispose ( ) ;
136
+ deferred . resolve ( editor ) ;
137
+ } else {
138
+ // In Theia, the promise resolves after opening the editor, but the editor is neither attached to the DOM, nor visible.
139
+ // This is a hack to first get an event from monaco after the widget update request, then IDE2 waits for the next monaco context key event.
140
+ // Here, the monaco context key event is not used, but this is the first event after the editor is visible in the UI.
141
+ disposables . push (
142
+ ( editor . editor as MonacoEditor ) . onDidResize ( ( dimension ) => {
143
+ if ( dimension ) {
144
+ const isKeyOwner = (
145
+ arg : unknown
146
+ ) : arg is { key : string } => {
147
+ if ( typeof arg === 'object' ) {
148
+ const object = arg as Record < string , unknown > ;
149
+ return typeof object [ 'key' ] === 'string' ;
150
+ }
151
+ return false ;
152
+ } ;
153
+ disposables . push (
154
+ this . contextKeyService . onDidChangeContext ( ( e ) => {
155
+ // `commentIsEmpty` is the first context key change event received from monaco after the editor is for real visible in the UI.
156
+ if ( isKeyOwner ( e ) && e . key === 'commentIsEmpty' ) {
157
+ deferred . resolve ( editor ) ;
158
+ disposables . dispose ( ) ;
159
+ }
160
+ } )
161
+ ) ;
162
+ }
163
+ } )
164
+ ) ;
165
+ }
166
+ }
167
+ } )
168
+ ) ;
169
+ this . editorManager . open (
89
170
new URI ( uri ) ,
90
171
options ?? {
91
172
mode : 'reveal' ,
92
173
preview : false ,
93
174
counter : 0 ,
94
175
}
95
176
) ;
177
+ const timeout = 5_000 ; // number of ms IDE2 waits for the editor to show up in the UI
178
+ const result = await Promise . race ( [
179
+ deferred . promise ,
180
+ wait ( timeout ) . then ( ( ) => {
181
+ disposables . dispose ( ) ;
182
+ return 'timeout' ;
183
+ } ) ,
184
+ ] ) ;
185
+ if ( result === 'timeout' ) {
186
+ console . warn (
187
+ `Timeout after ${ timeout } millis. The editor has not shown up in time. URI: ${ uri } `
188
+ ) ;
189
+ }
190
+ return result ;
96
191
}
97
192
}
98
193
}
0 commit comments