Skip to content

Commit b53307c

Browse files
authored
Merge pull request #10 from alex35mil/restart
Add restart editor option
2 parents 9231942 + a559579 commit b53307c

File tree

4 files changed

+109
-20
lines changed

4 files changed

+109
-20
lines changed

NeoHub/App.swift

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ extension KeyboardShortcuts.Name {
1212
"toggleSwitcher",
1313
default: .init(.n, modifiers: [.command, .control])
1414
)
15+
16+
static let restartEditor = Self("restartEditor")
1517
}
1618

1719
@main

NeoHub/EditorStore.swift

+95-18
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ final class EditorStore: ObservableObject {
99
let switcherWindow: SwitcherWindowRef
1010
let activationManager: ActivationManager
1111

12+
private var restartPoller: Timer?
13+
1214
init(activationManager: ActivationManager, switcherWindow: SwitcherWindowRef) {
1315
self.editors = [:]
1416
self.switcherWindow = switcherWindow
1517
self.activationManager = activationManager
18+
19+
KeyboardShortcuts.onKeyUp(for: .restartEditor) { [self] in
20+
self.restartActiveEditor()
21+
}
1622
}
1723

1824
public enum SortTarget {
@@ -48,24 +54,24 @@ final class EditorStore: ObservableObject {
4854
}
4955
}
5056

51-
func runEditor(req: RunRequest) {
57+
func runEditor(request: RunRequest) {
5258
log.info("Running an editor...")
5359

54-
let editorID = switch req.path {
60+
let editorID = switch request.path {
5561
case nil, "":
56-
EditorID(req.wd)
62+
EditorID(request.wd)
5763
case .some(let path):
5864
EditorID(
5965
URL(
6066
fileURLWithPath: path,
61-
relativeTo: req.wd
67+
relativeTo: request.wd
6268
)
6369
)
6470
}
6571

6672
log.info("Editor ID: \(editorID)")
6773

68-
let editorName = switch req.name {
74+
let editorName = switch request.name {
6975
case nil, "":
7076
editorID.lastPathComponent
7177
case .some(let name):
@@ -82,31 +88,31 @@ final class EditorStore: ObservableObject {
8288
log.info("No editors at \(editorID) found. Launching a new one.")
8389

8490
do {
85-
log.info("Running editor at \(req.wd.path)")
91+
log.info("Running editor at \(request.wd.path)")
8692

8793
let process = Process()
8894

89-
process.executableURL = req.bin
95+
process.executableURL = request.bin
9096

9197
let nofork = "--no-fork"
9298

93-
process.arguments = req.opts
99+
process.arguments = request.opts
94100

95101
if !process.arguments!.contains(nofork) {
96102
process.arguments!.append(nofork)
97103
}
98104

99-
if let path = req.path {
105+
if let path = request.path {
100106
process.arguments!.append(path)
101107
}
102108

103-
process.currentDirectoryURL = req.wd
104-
process.environment = req.env
109+
process.currentDirectoryURL = request.wd
110+
process.environment = request.env
105111

106112
process.terminationHandler = { process in
107113
DispatchQueue.main.async {
108114
log.info("Removing editor from the hub")
109-
self.editors[editorID] = nil
115+
self.editors.removeValue(forKey: editorID)
110116
}
111117
}
112118

@@ -127,7 +133,8 @@ final class EditorStore: ObservableObject {
127133
self.editors[editorID] = Editor(
128134
id: editorID,
129135
name: editorName,
130-
process: process
136+
process: process,
137+
request: request
131138
)
132139
}
133140
} else {
@@ -138,10 +145,10 @@ final class EditorStore: ObservableObject {
138145
"EditorID": editorID,
139146
"EditorPID": process.processIdentifier,
140147
"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,
145152
]
146153
)
147154
}
@@ -153,13 +160,81 @@ final class EditorStore: ObservableObject {
153160
}
154161
}
155162

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+
156222
func quitAllEditors() async {
157223
await withTaskGroup(of: Void.self) { group in
158224
for (_, editor) in self.editors {
159225
group.addTask { editor.quit() }
160226
}
161227
}
162228
}
229+
230+
private func invalidateRestartPoller() {
231+
log.debug("Stopping the restart poller")
232+
self.restartPoller?.invalidate()
233+
}
234+
235+
deinit {
236+
self.invalidateRestartPoller()
237+
}
163238
}
164239

165240
struct EditorID {
@@ -204,12 +279,14 @@ final class Editor: Identifiable {
204279

205280
private let process: Process
206281
private(set) var lastAcceessTime: Date
282+
private(set) var request: RunRequest
207283

208-
init(id: EditorID, name: String, process: Process) {
284+
init(id: EditorID, name: String, process: Process, request: RunRequest) {
209285
self.id = id
210286
self.name = name
211287
self.process = process
212288
self.lastAcceessTime = Date()
289+
self.request = request
213290
}
214291

215292
var displayPath: String {

NeoHub/SocketServer.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class MessageHandler: ChannelInboundHandler {
137137
)
138138

139139
DispatchQueue.global().async {
140-
self.store.runEditor(req: req)
140+
self.store.runEditor(request: req)
141141
}
142142
} catch {
143143
let error = ReportableError("Failed to decode request from the CLI", error: error)

NeoHub/views/SettingsView.swift

+11-1
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,22 @@ struct SettingsView: View {
3131
Divider().padding(.horizontal)
3232

3333
HStack {
34-
Text("NeoHub Hotkey")
34+
Text("Toggle Editor Selector")
3535
Spacer()
3636
KeyboardShortcuts.Recorder("", name: .toggleSwitcher)
3737
}
3838
.padding(.horizontal)
3939
.padding(.vertical, 10)
40+
41+
Divider().padding(.horizontal)
42+
43+
HStack {
44+
Text("Restart Active Editor")
45+
Spacer()
46+
KeyboardShortcuts.Recorder("", name: .restartEditor)
47+
}
48+
.padding(.horizontal)
49+
.padding(.vertical, 10)
4050
}
4151
.settingsGroup()
4252
Text("CLI").font(.title)

0 commit comments

Comments
 (0)