Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tail config #484

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3c092a0
Added Toggle Tail config in Debug.
sjoshid Dec 10, 2018
c60564e
Refactoring and test case as per @mmatoszko comments.
sjoshid Dec 14, 2018
335e0c1
Allowing each file to be tailed.
sjoshid Feb 3, 2019
7a1ae59
Complementing toggleTail flag instead of calling .toggle() because Tr…
sjoshid Feb 3, 2019
b374473
Removing test case as core doesn't return us anything.
sjoshid Feb 3, 2019
b0958ae
Core needs to send toggle tail config changed notification to client.
sjoshid Feb 4, 2019
322794f
Merge remote-tracking branch 'refs/remotes/upstream/master'
sjoshid Feb 4, 2019
f817741
Refactoring.
sjoshid Feb 4, 2019
dfec6fe
Adding function to test.
sjoshid Feb 4, 2019
eb98140
Merge remote-tracking branch 'refs/remotes/upstream/master'
sjoshid Feb 9, 2019
026f604
Refactoring based on new changes in upstream.
sjoshid Feb 9, 2019
b23d620
Merge branch 'master' of https://github.com/sjoshid/xi-mac
sjoshid Feb 9, 2019
63bda43
Removing unecessary file
sjoshid Feb 9, 2019
b8bfd74
Merge remote-tracking branch 'refs/remotes/upstream/master'
sjoshid Feb 13, 2019
c6c8d48
Adding xi-editor
sjoshid Nov 16, 2019
6477643
Merge remote-tracking branch 'refs/remotes/upstream/master'
sjoshid Nov 16, 2019
605b794
Fixing conflicts
sjoshid Nov 23, 2019
7dc6f4f
#922
sjoshid Nov 23, 2019
775092e
Merge branch 'master' of https://github.com/xi-editor/xi-mac into Add…
sjoshid Dec 14, 2019
762bcf1
Added Toggle Tail config in Debug.
sjoshid Dec 10, 2018
8d3a7ae
Merge upstream. (+2 squashed commits)
sjoshid Feb 9, 2019
b08133f
Handling toggleTailChanged notification from core.
sjoshid Nov 23, 2019
2ada878
Resolving conflicts
sjoshid Dec 15, 2019
4d5e738
Point to core's branch that has Tail changes.
sjoshid Dec 16, 2019
05ad2bf
Revert: Point to xi master.
sjoshid Dec 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Sources/XiEditor/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
7 changes: 7 additions & 0 deletions Sources/XiEditor/ClientImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions Sources/XiEditor/Core/CoreNotification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)")
Expand Down
25 changes: 25 additions & 0 deletions Sources/XiEditor/EditViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 }
Expand Down
7 changes: 7 additions & 0 deletions Sources/XiEditor/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,12 @@ Gw
<action selector="openErrorLogFolder:" target="azf-ih-6fI" id="dlx-rd-R59"/>
</connections>
</menuItem>
<menuItem title="Tail File" toolTip="Start tailing current file" id="ERX-yh-iVz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="debugToggleTail:" target="7er-QZ-amI" id="Nk3-gD-C6i"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
Expand Down Expand Up @@ -1227,6 +1233,7 @@ Gw
</connections>
</customObject>
<userDefaultsController id="yco-8S-n0A"/>
<userDefaultsController id="auS-BU-5TV"/>
</objects>
<point key="canvasLocation" x="-295" y="-593"/>
</scene>
Expand Down
83 changes: 43 additions & 40 deletions Sources/XiEditor/RPCSending.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -63,12 +63,12 @@ class StdoutRPCSender: RPCSending {
weak var client: XiClient?
private let rpcLogWriter: FileWriter?
private var lastLogs = CircleBuffer<String>(capacity: 100)

// RPC state
private var queue = DispatchQueue(label: "io.xi-editor.XiEditor.CoreConnection", attributes: [])
private var rpcIndex = 0
private var pending = Dictionary<Int, RpcCallback>()

init(path: String, errorLogDirectory: URL?) {
if let rpcLogPath = ProcessInfo.processInfo.environment[XI_RPC_LOG] {
self.rpcLogWriter = FileWriter(path: rpcLogPath)
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -161,44 +161,44 @@ class StdoutRPCSender: RPCSending {
} else {
handleRaw(bytes)
}

i = j + 1
}
}

if i < buffer.endIndex {
recvBuf.append(Data(buffer[i..<buffer.endIndex]))
}
}
}

private func sendJson(_ json: Any) {
do {
var data = try JSONSerialization.data(withJSONObject: json, options: [])
data.append(NEW_LINE, count: 1)

if let writer = self.rpcLogWriter {
writer.write(bytes: CLIENT_LOG_PREFIX)
writer.write(bytes: data)
}

inHandle.write(data as Data)
} catch _ {
print("error serializing to json")
}
}

private func sendResult(id: Any, result: Any) {
let json = ["id": id, "result": result]
sendJson(json)
}

private func handleRaw(_ data: Data) {
if let writer = self.rpcLogWriter {
writer.write(bytes: CORE_LOG_PREFIX)
writer.write(bytes: data)
}

Trace.shared.trace("handleRaw", .rpc, .begin)
do {
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
Expand All @@ -208,7 +208,7 @@ class StdoutRPCSender: RPCSending {
}
Trace.shared.trace("handleRaw", .rpc, .end)
}

/// handle a JSON RPC call. Determines whether it is a request, response or notification
/// and executes/responds accordingly
private func handleRpc(_ json: Any) {
Expand All @@ -234,84 +234,86 @@ class StdoutRPCSender: RPCSending {
self.handleNotification(json: obj)
}
}

private func handleRequest(json: [String: Any]) {
guard let request = CoreRequest.fromJson(json) else {
return
}

switch request {
case let .measureWidth(id, params):
guard let result = client?.measureWidth(args: params) else {
assertionFailure("measure_width request from core failed: \(params)")
return
}

sendResult(id: id, result: result)
}
}

private func handleNotification(json: [String: AnyObject]) {
guard let notification = CoreNotification.fromJson(json) else {
return
}

switch notification {
case let .alert(message):
self.client?.alert(text: message)

case let .updateCommands(viewIdentifier, plugin, commands):
self.client?.updateCommands(viewIdentifier: viewIdentifier,
plugin: plugin,
commands: commands)

case let .scrollTo(viewIdentifier, line, column):
self.client?.scroll(viewIdentifier: viewIdentifier, line: line, column: column)

case let .addStatusItem(viewIdentifier, source, key, value, alignment):
self.client?.addStatusItem(viewIdentifier: viewIdentifier, source: source, key: key, value: value, alignment: alignment)
case let .updateStatusItem(viewIdentifier, key, value):
self.client?.updateStatusItem(viewIdentifier: viewIdentifier, key: key, value: value)
case let .removeStatusItem(viewIdentifier, key):
self.client?.removeStatusItem(viewIdentifier: viewIdentifier, key: key)

case let .update(viewIdentifier, params):
self.client?.update(viewIdentifier: viewIdentifier,
params: params, rev: nil)

case let .configChanged(viewIdentifier, config):
self.client?.configChanged(viewIdentifier: viewIdentifier, changes: config)

case let .defStyle(params):
self.client?.defineStyle(params: params)

case let .availablePlugins(viewIdentifier, plugins):
self.client?.availablePlugins(viewIdentifier: viewIdentifier, plugins: plugins)
case let .pluginStarted(viewIdentifier, plugin):
self.client?.pluginStarted(viewIdentifier: viewIdentifier, pluginName: plugin)
case let .pluginStopped(viewIdentifier, pluginName):
self.client?.pluginStopped(viewIdentifier: viewIdentifier, pluginName: pluginName)

case let .availableThemes(themes):
self.client?.availableThemes(themes: themes)
case let .themeChanged(name, theme):
self.client?.themeChanged(name: name, theme: theme)

case let .availableLanguages(languages):
self.client?.availableLanguages(languages: languages)
case let .languageChanged(viewIdentifier, languageIdentifier):
self.client?.languageChanged(viewIdentifier: viewIdentifier, languageIdentifier: languageIdentifier)

case let .showHover(viewIdentifier, requestIdentifier, result):
self.client?.showHover(viewIdentifier: viewIdentifier, requestIdentifier: requestIdentifier, result: result)

case let .findStatus(viewIdentifier, status):
self.client?.findStatus(viewIdentifier: viewIdentifier, status: status)
case let .replaceStatus(viewIdentifier, status):
self.client?.replaceStatus(viewIdentifier: viewIdentifier, status: status)
case let .toggleTailChanged(viewIdentifier, isTailEnabled):
self.client?.toggleTailConfigChanged(viewIdentifier: viewIdentifier, isTailEnabled: isTailEnabled)
}
}

/// send an RPC request, returning immediately. The callback will be called when the
/// response comes in, likely from a different thread
func sendRpcAsync(_ method: String, params: Any, callback: RpcCallback? = nil) {
Expand All @@ -328,14 +330,14 @@ class StdoutRPCSender: RPCSending {
sendJson(req as Any)
Trace.shared.trace("send \(method)", .rpc, .end)
}

/// send RPC synchronously, blocking until return. Note: there is no ordering guarantee on
/// when this function may return. In particular, an async notification sent by the core after
/// a response to a synchronous RPC may be delivered before it.
func sendRpc(_ method: String, params: Any) -> RpcResult {
let semaphore = DispatchSemaphore(value: 0)
var result: RpcResult? = nil

sendRpcAsync(method, params: params) { (r) in
result = r
semaphore.signal()
Expand All @@ -344,3 +346,4 @@ class StdoutRPCSender: RPCSending {
return result!
}
}

Loading