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