diff --git a/Sources/XiEditor/Client.swift b/Sources/XiEditor/Client.swift index 6c164225..973b9cec 100644 --- a/Sources/XiEditor/Client.swift +++ b/Sources/XiEditor/Client.swift @@ -86,4 +86,7 @@ protocol XiClient: AnyObject { /// A notification containing the current replace status. func replaceStatus(viewIdentifier: String, status: ReplaceStatus) + + /// A notification telling toggle tail config was successfully changed. + func toggleTailConfigChanged(viewIdentifier: String, isTailEnabled: Bool) } diff --git a/Sources/XiEditor/ClientImplementation.swift b/Sources/XiEditor/ClientImplementation.swift index a7e54296..8d32b50f 100644 --- a/Sources/XiEditor/ClientImplementation.swift +++ b/Sources/XiEditor/ClientImplementation.swift @@ -176,6 +176,13 @@ class ClientImplementation: XiClient, DocumentsProviding, ConfigCacheProviding, } } } + + func toggleTailConfigChanged(viewIdentifier: String, isTailEnabled: Bool) { + let document = documentForViewIdentifier(viewIdentifier: viewIdentifier) + DispatchQueue.main.async { + document?.editViewController?.toggleTailConfigChanged(isTailEnabled) + } + } // Stores the config dict so new windows don't have to wait for core to send it. // The main purpose of this is ensuring that `unified_titlebar` applies immediately. diff --git a/Sources/XiEditor/Core/CoreNotification.swift b/Sources/XiEditor/Core/CoreNotification.swift index d7422565..f8be8d5c 100644 --- a/Sources/XiEditor/Core/CoreNotification.swift +++ b/Sources/XiEditor/Core/CoreNotification.swift @@ -71,6 +71,7 @@ enum CoreNotification { case findStatus(viewIdentifier: ViewIdentifier, status: [FindStatus]) case replaceStatus(viewIdentifier: ViewIdentifier, status: ReplaceStatus) + case toggleTailChanged(viewIdentifier: ViewIdentifier, isTailEnabled: Bool) static func fromJson(_ json: [String: Any]) -> CoreNotification? { guard @@ -228,6 +229,11 @@ enum CoreNotification { { return .replaceStatus(viewIdentifier: viewIdentifier!, status: replaceStatus) } + case "toggle_tail_config_changed": + if let isTailEnabled = jsonParams["is_tail_enabled"] as? Bool + { + return .toggleTailChanged(viewIdentifier: viewIdentifier!, isTailEnabled: isTailEnabled) + } default: assertionFailure("Unsupported notification method from core: \(jsonMethod)") diff --git a/Sources/XiEditor/EditViewController.swift b/Sources/XiEditor/EditViewController.swift index 63218ab9..95eed132 100644 --- a/Sources/XiEditor/EditViewController.swift +++ b/Sources/XiEditor/EditViewController.swift @@ -150,6 +150,12 @@ class EditViewController: NSViewController, EditViewDataSource, FindDelegate, Sc updateLanguageMenu() } } + + var isTailEnabled: Bool = false { + didSet { + updateTailMenu() + } + } // used to calculate the gutter width. Initial -1 so that a new document // still triggers update of gutter width. @@ -719,6 +725,10 @@ class EditViewController: NSViewController, EditViewDataSource, FindDelegate, Sc document.xiCore.setTheme(themeName: sender.title) } + @IBAction func debugToggleTail(_ sender: NSMenuItem) { + document.xiCore.toggleTailConfig(identifier: document.coreViewIdentifier!, enabled: !self.isTailEnabled) + } + @IBAction func debugSetLanguage(_ sender: NSMenuItem) { guard sender.state != NSControl.StateValue.on else { print("language already active"); return } document.xiCore.setLanguage(identifier: document.coreViewIdentifier!, languageName: sender.title) @@ -848,11 +858,22 @@ class EditViewController: NSViewController, EditViewDataSource, FindDelegate, Sc item.state = findViewController.showMultipleSearchQueries ? .on : .off } + func updateTailMenu() { + let toggleTailSubMenu = NSApplication.shared.mainMenu!.item(withTitle: "Debug")!.submenu!.item(withTitle: "Tail File") + + if self.isTailEnabled { + toggleTailSubMenu!.state = NSControl.StateValue.on + } else { + toggleTailSubMenu!.state = NSControl.StateValue.off + } + } + // Gets called when active window changes func updateMenuState() { updatePluginMenu() updateLanguageMenu() updateFindMenu() + updateTailMenu() } @objc func handleCommand(_ sender: NSMenuItem) { @@ -927,6 +948,10 @@ class EditViewController: NSViewController, EditViewDataSource, FindDelegate, Sc languagesMenu.addItem(item) } } + + public func toggleTailConfigChanged(_ isTailEnabled: Bool) { + self.isTailEnabled = isTailEnabled + } @IBAction func gotoLine(_ sender: AnyObject) { guard let window = self.view.window else { return } diff --git a/Sources/XiEditor/Main.storyboard b/Sources/XiEditor/Main.storyboard index c810bd06..7bd20c4a 100644 --- a/Sources/XiEditor/Main.storyboard +++ b/Sources/XiEditor/Main.storyboard @@ -1198,6 +1198,12 @@ Gw + + + + + + @@ -1227,6 +1233,7 @@ Gw + diff --git a/Sources/XiEditor/RPCSending.swift b/Sources/XiEditor/RPCSending.swift index 7dcc44bb..1044a2d1 100644 --- a/Sources/XiEditor/RPCSending.swift +++ b/Sources/XiEditor/RPCSending.swift @@ -30,7 +30,7 @@ struct RemoteError { let code: Int let message: String let data: AnyObject? - + init?(json: [String: AnyObject]) { guard let code = json["code"] as? Int, let message = json["message"] as? String else { return nil } @@ -63,12 +63,12 @@ class StdoutRPCSender: RPCSending { weak var client: XiClient? private let rpcLogWriter: FileWriter? private var lastLogs = CircleBuffer(capacity: 100) - + // RPC state private var queue = DispatchQueue(label: "io.xi-editor.XiEditor.CoreConnection", attributes: []) private var rpcIndex = 0 private var pending = Dictionary() - + init(path: String, errorLogDirectory: URL?) { if let rpcLogPath = ProcessInfo.processInfo.environment[XI_RPC_LOG] { self.rpcLogWriter = FileWriter(path: rpcLogPath) @@ -88,18 +88,18 @@ class StdoutRPCSender: RPCSending { task.environment = ProcessInfo.processInfo.environment } task.environment?["RUST_BACKTRACE"] = "1" - + let outPipe = Pipe() task.standardOutput = outPipe let inPipe = Pipe() task.standardInput = inPipe inHandle = inPipe.fileHandleForWriting - + outPipe.fileHandleForReading.readabilityHandler = { handle in let data = handle.availableData self.recvHandler(data) } - + let errPipe = Pipe() task.standardError = errPipe errPipe.fileHandleForReading.readabilityHandler = { [weak self] handle in @@ -112,22 +112,22 @@ class StdoutRPCSender: RPCSending { self?.lastLogs.push(errString) } } - + // save backtrace on core crash task.terminationHandler = { [weak self] process in guard process.terminationStatus != 0, let strongSelf = self else { print("xi-core exited with code 0") return } - + print("xi-core exited with code \(process.terminationStatus), attempting to save log") - + let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd-HHMMSS" let timeStamp = dateFormatter.string(from: Date()) let crashLogFilename = "XiEditor-Crash-\(timeStamp).log" let crashLogPath = errorLogDirectory?.appendingPathComponent(crashLogFilename) - + let logText = strongSelf.lastLogs.allItems().joined() if let path = crashLogPath { do { @@ -140,12 +140,12 @@ class StdoutRPCSender: RPCSending { } task.launch() } - + private func recvHandler(_ data: Data) { if data.count == 0 { return } - + // Split incoming bytes into "packets" (separated by newlines) // and dispatch to the app to handle data.withUnsafeBytes { buffer in @@ -161,44 +161,44 @@ class StdoutRPCSender: RPCSending { } else { handleRaw(bytes) } - + i = j + 1 } } - + if i < buffer.endIndex { recvBuf.append(Data(buffer[i.. RpcResult { let semaphore = DispatchSemaphore(value: 0) var result: RpcResult? = nil - + sendRpcAsync(method, params: params) { (r) in result = r semaphore.signal() @@ -344,3 +346,4 @@ class StdoutRPCSender: RPCSending { return result! } } + diff --git a/Sources/XiEditor/XiCore.swift b/Sources/XiEditor/XiCore.swift index dd120d58..02dbbb4f 100644 --- a/Sources/XiEditor/XiCore.swift +++ b/Sources/XiEditor/XiCore.swift @@ -48,6 +48,9 @@ protocol XiCore: class { /// `Document` calls are migrated to it's own protocol. func sendRpcAsync(_ method: String, params: Any, callback: RpcCallback?) func sendRpc(_ method: String, params: Any) -> RpcResult + /// Will tail opened file if enabled. + /// If toggle succeeds the client will receive a `toggle_tail_config_changed` notification. + func toggleTailConfig(identifier: ViewIdentifier, enabled: Bool) } final class CoreConnection: XiCore { @@ -109,6 +112,10 @@ final class CoreConnection: XiCore { let params = ["destination": destination, "frontend_samples": frontendSamples] as AnyObject sendRpcAsync("save_trace", params: params) } + + func toggleTailConfig(identifier: ViewIdentifier, enabled: Bool) { + sendRpcAsync("toggle_tail", params: ["view_id": identifier, "enabled": enabled]) + } func sendRpcAsync(_ method: String, params: Any, callback: RpcCallback? = nil) { rpcSender.sendRpcAsync(method, params: params, callback: callback) diff --git a/Tests/IntegrationTests/TestClientImplementation.swift b/Tests/IntegrationTests/TestClientImplementation.swift index 4cde8f95..00e6a33b 100644 --- a/Tests/IntegrationTests/TestClientImplementation.swift +++ b/Tests/IntegrationTests/TestClientImplementation.swift @@ -86,4 +86,7 @@ class TestClientImplementation: XiClient { func replaceStatus(viewIdentifier: String, status: ReplaceStatus) { } + + func toggleTailConfigChanged(viewIdentifier: String, isTailEnabled: Bool) { + } } diff --git a/xi-editor b/xi-editor index a5850993..fd14ce69 160000 --- a/xi-editor +++ b/xi-editor @@ -1 +1 @@ -Subproject commit a58509934eeab8dc10a0ccf8b68b6d10c78a856c +Subproject commit fd14ce69bbbe80c011088ae47e50a9131217ea50