@@ -9,10 +9,16 @@ final class EditorStore: ObservableObject {
9
9
let switcherWindow : SwitcherWindowRef
10
10
let activationManager : ActivationManager
11
11
12
+ private var restartPoller : Timer ?
13
+
12
14
init ( activationManager: ActivationManager , switcherWindow: SwitcherWindowRef ) {
13
15
self . editors = [ : ]
14
16
self . switcherWindow = switcherWindow
15
17
self . activationManager = activationManager
18
+
19
+ KeyboardShortcuts . onKeyUp ( for: . restartEditor) { [ self ] in
20
+ self . restartActiveEditor ( )
21
+ }
16
22
}
17
23
18
24
public enum SortTarget {
@@ -48,24 +54,24 @@ final class EditorStore: ObservableObject {
48
54
}
49
55
}
50
56
51
- func runEditor( req : RunRequest ) {
57
+ func runEditor( request : RunRequest ) {
52
58
log. info ( " Running an editor... " )
53
59
54
- let editorID = switch req . path {
60
+ let editorID = switch request . path {
55
61
case nil , " " :
56
- EditorID ( req . wd)
62
+ EditorID ( request . wd)
57
63
case . some( let path) :
58
64
EditorID (
59
65
URL (
60
66
fileURLWithPath: path,
61
- relativeTo: req . wd
67
+ relativeTo: request . wd
62
68
)
63
69
)
64
70
}
65
71
66
72
log. info ( " Editor ID: \( editorID) " )
67
73
68
- let editorName = switch req . name {
74
+ let editorName = switch request . name {
69
75
case nil , " " :
70
76
editorID. lastPathComponent
71
77
case . some( let name) :
@@ -82,31 +88,31 @@ final class EditorStore: ObservableObject {
82
88
log. info ( " No editors at \( editorID) found. Launching a new one. " )
83
89
84
90
do {
85
- log. info ( " Running editor at \( req . wd. path) " )
91
+ log. info ( " Running editor at \( request . wd. path) " )
86
92
87
93
let process = Process ( )
88
94
89
- process. executableURL = req . bin
95
+ process. executableURL = request . bin
90
96
91
97
let nofork = " --no-fork "
92
98
93
- process. arguments = req . opts
99
+ process. arguments = request . opts
94
100
95
101
if !process. arguments!. contains ( nofork) {
96
102
process. arguments!. append ( nofork)
97
103
}
98
104
99
- if let path = req . path {
105
+ if let path = request . path {
100
106
process. arguments!. append ( path)
101
107
}
102
108
103
- process. currentDirectoryURL = req . wd
104
- process. environment = req . env
109
+ process. currentDirectoryURL = request . wd
110
+ process. environment = request . env
105
111
106
112
process. terminationHandler = { process in
107
113
DispatchQueue . main. async {
108
114
log. info ( " Removing editor from the hub " )
109
- self . editors [ editorID ] = nil
115
+ self . editors. removeValue ( forKey : editorID )
110
116
}
111
117
}
112
118
@@ -127,7 +133,8 @@ final class EditorStore: ObservableObject {
127
133
self . editors [ editorID] = Editor (
128
134
id: editorID,
129
135
name: editorName,
130
- process: process
136
+ process: process,
137
+ request: request
131
138
)
132
139
}
133
140
} else {
@@ -138,10 +145,10 @@ final class EditorStore: ObservableObject {
138
145
" EditorID " : editorID,
139
146
" EditorPID " : process. processIdentifier,
140
147
" EditorTerminationStatus " : process. terminationStatus,
141
- " EditorWorkingDirectory " : req . wd,
142
- " EditorBinary " : req . bin,
143
- " EditorPathArgument " : req . path ?? " - " ,
144
- " EditorOptions " : req . opts,
148
+ " EditorWorkingDirectory " : request . wd,
149
+ " EditorBinary " : request . bin,
150
+ " EditorPathArgument " : request . path ?? " - " ,
151
+ " EditorOptions " : request . opts,
145
152
]
146
153
)
147
154
}
@@ -153,13 +160,81 @@ final class EditorStore: ObservableObject {
153
160
}
154
161
}
155
162
163
+ func restartActiveEditor( ) {
164
+ log. info ( " Restarting the active editor... " )
165
+
166
+ guard let activeApp = NSWorkspace . shared. frontmostApplication else {
167
+ log. info ( " There is no active app. Canceling restart. " )
168
+ return
169
+ }
170
+
171
+ guard let editor = self . editors. first ( where: { id, editor in editor. processIdentifier == activeApp. processIdentifier } ) ? . value else {
172
+ log. info ( " The active app is not an editor. Canceling restart. " )
173
+ return
174
+ }
175
+
176
+ log. info ( " Quiting the editor " )
177
+
178
+ editor. quit ( )
179
+
180
+ // Termination handler should remove the editor from the store,
181
+ // so we should wait for that, then re-run the editor
182
+ log. info ( " Starting polling until the old editor is removed from the store " )
183
+
184
+ let timeout = TimeInterval ( 5 )
185
+ let startTime = Date ( )
186
+
187
+ self . restartPoller = Timer . scheduledTimer ( withTimeInterval: 0.005 , repeats: true ) { [ weak self] _ in
188
+ log. trace ( " Starting the iteration... " )
189
+
190
+ guard let self = self else { return }
191
+
192
+ log. trace ( " We have self. Checking the store. " )
193
+ if self . editors [ editor. id] == nil {
194
+ log. info ( " The old editor removed from the store. Starting the new instance. " )
195
+ self . invalidateRestartPoller ( )
196
+ self . runEditor ( request: editor. request)
197
+ } else if - startTime. timeIntervalSinceNow > timeout {
198
+ log. error ( " The editor wasn't removed from the store within the timeout. Canceling the restart. " )
199
+ self . invalidateRestartPoller ( )
200
+
201
+ let alert = NSAlert ( )
202
+
203
+ alert. messageText = " Failed to restart the editor "
204
+ alert. informativeText = " Please, report the issue on GitHub. "
205
+ alert. alertStyle = . critical
206
+ alert. addButton ( withTitle: " Report " )
207
+ alert. addButton ( withTitle: " Dismiss " )
208
+
209
+ switch alert. runModal ( ) {
210
+ case . alertFirstButtonReturn:
211
+ let error = ReportableError ( " Failed to restart the editor " )
212
+ BugReporter . report ( error)
213
+ default : ( )
214
+ }
215
+
216
+ return
217
+ }
218
+ }
219
+ }
220
+
221
+
156
222
func quitAllEditors( ) async {
157
223
await withTaskGroup ( of: Void . self) { group in
158
224
for (_, editor) in self . editors {
159
225
group. addTask { editor. quit ( ) }
160
226
}
161
227
}
162
228
}
229
+
230
+ private func invalidateRestartPoller( ) {
231
+ log. debug ( " Stopping the restart poller " )
232
+ self . restartPoller? . invalidate ( )
233
+ }
234
+
235
+ deinit {
236
+ self . invalidateRestartPoller ( )
237
+ }
163
238
}
164
239
165
240
struct EditorID {
@@ -204,12 +279,14 @@ final class Editor: Identifiable {
204
279
205
280
private let process : Process
206
281
private( set) var lastAcceessTime : Date
282
+ private( set) var request : RunRequest
207
283
208
- init ( id: EditorID , name: String , process: Process ) {
284
+ init ( id: EditorID , name: String , process: Process , request : RunRequest ) {
209
285
self . id = id
210
286
self . name = name
211
287
self . process = process
212
288
self . lastAcceessTime = Date ( )
289
+ self . request = request
213
290
}
214
291
215
292
var displayPath : String {
0 commit comments