1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-11-25 06:06:21 +03:00

Distribute MainWindow in 4 files

This commit is contained in:
Tae Won Ha 2017-09-03 23:11:33 +02:00
parent 0e52dca2f4
commit 1cd2db1ba5
No known key found for this signature in database
GPG Key ID: E40743465B5B8B44
5 changed files with 591 additions and 557 deletions

View File

@ -14,7 +14,6 @@
1929B0BE6C984A25E8D2F128 /* ImageAndTextTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BDC3F82CB4CB4FE56D1B /* ImageAndTextTableCell.swift */; };
1929B0E0C3BC59F52713D5A2 /* FoundationCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B9AF20D7BD6E5C975128 /* FoundationCommons.swift */; };
1929B0F599D1F62C7BE53D2C /* HttpServerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1DC584C89C477E83FA2 /* HttpServerService.swift */; };
1929B0FF696312F754BC96E2 /* MainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD8CBADC191CF8C85309 /* MainWindow.swift */; };
1929B17F6D4F0DF4F86544C4 /* ThemedTableSubviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD2CA8DD198A6BCDBCB7 /* ThemedTableSubviews.swift */; };
1929B1837C750CADB3A5BCB9 /* OpenQuicklyFileViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1558455B3A74D93EF2A /* OpenQuicklyFileViewRow.swift */; };
1929B18A0D7C7407C51DB642 /* DataWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 1929BB6CFF4CC0B5E8B00C62 /* DataWrapper.m */; };
@ -25,6 +24,7 @@
1929B333855A5406C400DA92 /* OpenQuicklyFilterOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BEDE7F92BC7B49E802AF /* OpenQuicklyFilterOperation.swift */; };
1929B3557317755A43513B17 /* OpenQuicklyWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B71A92C24FEFE79A851E /* OpenQuicklyWindow.swift */; };
1929B370A275F8C70AD5AA08 /* UrlCommonsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B11D672134E52A256A7F /* UrlCommonsTest.swift */; };
1929B3A6C332FFAAEC7FD219 /* MainWindow+CustomTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B71B4BB6550F5BC6D4CF /* MainWindow+CustomTitle.swift */; };
1929B3AC66EFE35D68C020E3 /* PreviewToolReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BFB0F294F3714D5E095F /* PreviewToolReducer.swift */; };
1929B3F5743967125F357C9F /* Matcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BEEB33113B0E33C3830F /* Matcher.swift */; };
1929B462CD4935AFF6D69457 /* FileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B7CB4863F80230C32D3C /* FileItem.swift */; };
@ -34,6 +34,7 @@
1929B4F0612224E594E89B92 /* AppearancePref.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B0FBFB766042CF06E463 /* AppearancePref.swift */; };
1929B4FEE6EB56EF3F56B805 /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B34FC23D805A8B29E8F7 /* Context.swift */; };
1929B50D933A369A86A165DE /* AdvencedPref.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BBE0A534F2F6009D31BE /* AdvencedPref.swift */; };
1929B5257DB27F03C6663482 /* MainWindow+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B067B3247675BCD09218 /* MainWindow+Actions.swift */; };
1929B53876E6952D378C2B30 /* ScoredFileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BDF9EBAF1D9D44399045 /* ScoredFileItem.swift */; };
1929B542A071BD03C846F6EF /* PrefUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B8241CDE58F7AAF89AE4 /* PrefUtils.swift */; };
1929B5543B1E31A26096E656 /* FileMonitorReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B04EC69F616EEFAF5F96 /* FileMonitorReducer.swift */; };
@ -52,6 +53,7 @@
1929B71610FF1DC6E459BA49 /* PreviewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B8EF9A9F5ACC175452BD /* PreviewUtils.swift */; };
1929B728262BAA14FC93F6AC /* NeoVimView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BF00B466B40629C2AABE /* NeoVimView.swift */; };
1929B7993C8DB7F59447DF5F /* NeoVimView+Draw.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B19207FBC2EBDF1B88F3 /* NeoVimView+Draw.swift */; };
1929B8DDACEB28E6672AEC42 /* MainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B6E01216D49BB9F3B6A3 /* MainWindow.swift */; };
1929B8E90A1378E494D481E7 /* PrefUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B7BB3E4B3DC96284B686 /* PrefUtilsTest.swift */; };
1929B8FB248D71BF88A35761 /* PreviewTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B6C6C7792B05164B0216 /* PreviewTool.swift */; };
1929B9318D32146D58BB38EC /* AppKitCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A70931D60E04200E12030 /* AppKitCommons.swift */; };
@ -81,6 +83,7 @@
1929BDD4BFA4175D0A1B0BC3 /* NeoVimView+Api.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD8F08E21E3ECE9F84AB /* NeoVimView+Api.swift */; };
1929BDFDBDA7180D02ACB37E /* RxSwiftCommonsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B6C215ACCBE12672A8D7 /* RxSwiftCommonsTest.swift */; };
1929BE0DAEE9664C5BCFA211 /* States.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BB6608B4F0E037CA0F4C /* States.swift */; };
1929BE0F64A6CE5BCE2A5092 /* MainWindow+Delegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B714EB137AE448CE8ABD /* MainWindow+Delegates.swift */; };
1929BE2F3E0182CC51F2763A /* ThemedTableSubviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD2CA8DD198A6BCDBCB7 /* ThemedTableSubviews.swift */; };
1929BE407A667356E29386EF /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B96384F1ED979BD001C556F /* Logger.swift */; };
1929BEAE0592096BC1191B67 /* PrefPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B07A4A9209C88380E015 /* PrefPane.swift */; };
@ -404,6 +407,7 @@
/* Begin PBXFileReference section */
1929B02440BC99C42F9EBD45 /* NetUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetUtils.m; sourceTree = "<group>"; };
1929B04EC69F616EEFAF5F96 /* FileMonitorReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileMonitorReducer.swift; sourceTree = "<group>"; };
1929B067B3247675BCD09218 /* MainWindow+Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainWindow+Actions.swift"; sourceTree = "<group>"; };
1929B07A4A9209C88380E015 /* PrefPane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefPane.swift; sourceTree = "<group>"; };
1929B0EB3F49C42A57D083AF /* GeneralPrefReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralPrefReducer.swift; sourceTree = "<group>"; };
1929B0EEBE4A765934AF8335 /* DataWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataWrapper.h; sourceTree = "<group>"; };
@ -435,8 +439,11 @@
1929B6AD3396160AA2C46919 /* Debouncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = "<group>"; };
1929B6C215ACCBE12672A8D7 /* RxSwiftCommonsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxSwiftCommonsTest.swift; sourceTree = "<group>"; };
1929B6C6C7792B05164B0216 /* PreviewTool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewTool.swift; sourceTree = "<group>"; };
1929B6E01216D49BB9F3B6A3 /* MainWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindow.swift; sourceTree = "<group>"; };
1929B7039C5689CE45F53888 /* AdvancedPrefReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedPrefReducer.swift; sourceTree = "<group>"; };
1929B714EB137AE448CE8ABD /* MainWindow+Delegates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainWindow+Delegates.swift"; sourceTree = "<group>"; };
1929B71A92C24FEFE79A851E /* OpenQuicklyWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenQuicklyWindow.swift; sourceTree = "<group>"; };
1929B71B4BB6550F5BC6D4CF /* MainWindow+CustomTitle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainWindow+CustomTitle.swift"; sourceTree = "<group>"; };
1929B7A68B7109CEFAF105E8 /* AppDelegateReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegateReducer.swift; sourceTree = "<group>"; };
1929B7BB3E4B3DC96284B686 /* PrefUtilsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefUtilsTest.swift; sourceTree = "<group>"; };
1929B7CB4863F80230C32D3C /* FileItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItem.swift; sourceTree = "<group>"; };
@ -468,7 +475,6 @@
1929BD2CA8DD198A6BCDBCB7 /* ThemedTableSubviews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemedTableSubviews.swift; sourceTree = "<group>"; };
1929BD4149D5A25C82064DD8 /* UiRoot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UiRoot.swift; sourceTree = "<group>"; };
1929BD83A13BF133741766CC /* MainWindowReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowReducer.swift; sourceTree = "<group>"; };
1929BD8CBADC191CF8C85309 /* MainWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindow.swift; sourceTree = "<group>"; };
1929BD8F08E21E3ECE9F84AB /* NeoVimView+Api.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NeoVimView+Api.swift"; sourceTree = "<group>"; };
1929BDC3F82CB4CB4FE56D1B /* ImageAndTextTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageAndTextTableCell.swift; sourceTree = "<group>"; };
1929BDC8F5D48578A90236E9 /* FileBrowserReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileBrowserReducer.swift; sourceTree = "<group>"; };
@ -731,7 +737,6 @@
1929B32401E8914DE9BF76CA /* Components */ = {
isa = PBXGroup;
children = (
1929BD8CBADC191CF8C85309 /* MainWindow.swift */,
1929BD4149D5A25C82064DD8 /* UiRoot.swift */,
1929B6C6C7792B05164B0216 /* PreviewTool.swift */,
1929B365A6434354B568B04F /* FileMonitor.swift */,
@ -741,6 +746,7 @@
1929BD9EEC30C0A498877E5B /* Open Quickly */,
1929B85023B042C485409CE1 /* HtmlPreviewTool.swift */,
1929BC2F05E9A5C0DB039739 /* Theme.swift */,
1929BBEE2FFA397A0691CC25 /* MainWindow */,
);
name = Components;
sourceTree = "<group>";
@ -810,6 +816,17 @@
name = Preferences;
sourceTree = "<group>";
};
1929BBEE2FFA397A0691CC25 /* MainWindow */ = {
isa = PBXGroup;
children = (
1929B71B4BB6550F5BC6D4CF /* MainWindow+CustomTitle.swift */,
1929B6E01216D49BB9F3B6A3 /* MainWindow.swift */,
1929B067B3247675BCD09218 /* MainWindow+Actions.swift */,
1929B714EB137AE448CE8ABD /* MainWindow+Delegates.swift */,
);
name = MainWindow;
sourceTree = "<group>";
};
1929BC56ADBA3275E7A0A598 /* Preferences */ = {
isa = PBXGroup;
children = (
@ -1689,7 +1706,6 @@
1929BE0DAEE9664C5BCFA211 /* States.swift in Sources */,
4BE149931EEF4792003DE5E2 /* Socket+Server.swift in Sources */,
1929B4FEE6EB56EF3F56B805 /* Context.swift in Sources */,
1929B0FF696312F754BC96E2 /* MainWindow.swift in Sources */,
4BE149901EEF4792003DE5E2 /* Scopes.swift in Sources */,
1929BD3878A3A47B8D685CD2 /* AppDelegateReducer.swift in Sources */,
1929BAFF1E011321D3186EE6 /* UiRoot.swift in Sources */,
@ -1748,6 +1764,10 @@
1929BE2F3E0182CC51F2763A /* ThemedTableSubviews.swift in Sources */,
1929B6460862447A31B5B082 /* ImageAndTextTableCell.swift in Sources */,
1929BBE28654E4307AF1E2FD /* Theme.swift in Sources */,
1929B3A6C332FFAAEC7FD219 /* MainWindow+CustomTitle.swift in Sources */,
1929B8DDACEB28E6672AEC42 /* MainWindow.swift in Sources */,
1929B5257DB27F03C6663482 /* MainWindow+Actions.swift in Sources */,
1929BE0F64A6CE5BCE2A5092 /* MainWindow+Delegates.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,179 @@
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
// MARK: - File Menu Item Actions
extension MainWindow {
@IBAction func newTab(_ sender: Any?) {
self.neoVimView.newTab()
}
@IBAction func openDocument(_ sender: Any?) {
let panel = NSOpenPanel()
panel.canChooseDirectories = true
panel.allowsMultipleSelection = true
panel.beginSheetModal(for: self.window) { result in
guard result == NSFileHandlingPanelOKButton else {
return
}
let urls = panel.urls
if self.neoVimView.allBuffers().count == 1 {
let isTransient = self.neoVimView.allBuffers().first?.isTransient ?? false
if isTransient {
self.neoVimView.cwd = FileUtils.commonParent(of: urls)
}
}
self.neoVimView.open(urls: urls)
}
}
@IBAction func openQuickly(_ sender: Any?) {
self.emit(self.uuidAction(for: .openQuickly))
}
@IBAction func saveDocument(_ sender: Any?) {
guard let curBuf = self.neoVimView.currentBuffer() else {
return
}
if curBuf.url == nil {
self.savePanelSheet { self.neoVimView.saveCurrentTab(url: $0) }
return
}
self.neoVimView.saveCurrentTab()
}
@IBAction func saveDocumentAs(_ sender: Any?) {
if self.neoVimView.currentBuffer() == nil {
return
}
self.savePanelSheet { url in
self.neoVimView.saveCurrentTab(url: url)
if self.neoVimView.isCurrentBufferDirty() {
self.neoVimView.openInNewTab(urls: [url])
} else {
self.neoVimView.openInCurrentTab(url: url)
}
}
}
fileprivate func savePanelSheet(action: @escaping (URL) -> Void) {
let panel = NSSavePanel()
panel.beginSheetModal(for: self.window) { result in
guard result == NSFileHandlingPanelOKButton else {
return
}
let showAlert: () -> Void = {
let alert = NSAlert()
alert.addButton(withTitle: "OK")
alert.messageText = "Invalid File Name"
alert.informativeText = "The file name you have entered cannot be used. Please use a different name."
alert.alertStyle = .warning
alert.runModal()
}
guard let url = panel.url else {
showAlert()
return
}
action(url)
}
}
}
// MARK: - Tools Menu Item Actions
extension MainWindow {
@IBAction func toggleAllTools(_ sender: Any?) {
self.workspace.toggleAllTools()
self.focusNeoVimView(self)
self.emit(self.uuidAction(for: .toggleAllTools(self.workspace.isAllToolsVisible)))
}
@IBAction func toggleToolButtons(_ sender: Any?) {
self.workspace.toggleToolButtons()
self.emit(self.uuidAction(for: .toggleToolButtons(self.workspace.isToolButtonsVisible)))
}
@IBAction func toggleFileBrowser(_ sender: Any?) {
let fileBrowser = self.fileBrowserContainer
if fileBrowser?.isSelected == true {
if fileBrowser?.view.isFirstResponder == true {
fileBrowser?.toggle()
self.focusNeoVimView(self)
} else {
self.emit(self.uuidAction(for: .focus(.fileBrowser)))
}
return
}
fileBrowser?.toggle()
self.emit(self.uuidAction(for: .focus(.fileBrowser)))
}
@IBAction func focusNeoVimView(_: Any?) {
// self.window.makeFirstResponder(self.neoVimView)
self.emit(self.uuidAction(for: .focus(.neoVimView)))
}
}
// MARK: - NSUserInterfaceValidationsProtocol
extension MainWindow {
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
let canSave = self.neoVimView.currentBuffer() != nil
let canSaveAs = canSave
let canOpen = canSave
let canOpenQuickly = canSave
let canFocusNeoVimView = self.window.firstResponder != self.neoVimView
let canToggleFileBrowser = self.tools.keys.contains(.fileBrowser)
let canToggleTools = !self.tools.isEmpty
guard let action = item.action else {
return true
}
switch action {
case #selector(toggleAllTools(_:)), #selector(toggleToolButtons(_:)):
return canToggleTools
case #selector(toggleFileBrowser(_:)):
return canToggleFileBrowser
case #selector(focusNeoVimView(_:)):
return canFocusNeoVimView
case #selector(openDocument(_:)):
return canOpen
case #selector(openQuickly(_:)):
return canOpenQuickly
case #selector(saveDocument(_:)):
return canSave
case #selector(saveDocumentAs(_:)):
return canSaveAs
default:
return true
}
}
}

View File

@ -0,0 +1,147 @@
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
import PureLayout
// MARK: - Custom title
extension MainWindow {
func themeTitlebar(grow: Bool) {
if self.window.styleMask.contains(.fullScreen) {
return
}
let prevFirstResponder = self.window.firstResponder
self.window.titlebarAppearsTransparent = true
self.workspace.removeFromSuperview()
self.set(repUrl: self.window.representedURL, themed: true)
self.window.contentView?.addSubview(self.workspace)
self.workspace.autoPinEdge(toSuperviewEdge: .top, withInset: 22)
self.workspace.autoPinEdge(toSuperviewEdge: .right)
self.workspace.autoPinEdge(toSuperviewEdge: .bottom)
self.workspace.autoPinEdge(toSuperviewEdge: .left)
self.titlebarThemed = true
self.window.makeFirstResponder(prevFirstResponder)
}
func unthemeTitlebar(dueFullScreen: Bool) {
self.clearCustomTitle()
guard let contentView = self.window.contentView else {
return
}
let prevFrame = window.frame
window.titlebarAppearsTransparent = false
self.workspace.removeFromSuperview()
self.window.titleVisibility = .visible
self.window.styleMask.remove(.fullSizeContentView)
self.set(repUrl: self.window.representedURL, themed: false)
contentView.addSubview(self.workspace)
self.workspace.autoPinEdgesToSuperviewEdges()
if !dueFullScreen {
self.window.setFrame(prevFrame, display: true, animate: false)
self.titlebarThemed = false
}
}
func set(repUrl url: URL?, themed: Bool) {
if self.window.styleMask.contains(.fullScreen) || themed == false {
self.internalSetRepUrl(url)
return
}
let prevFirstResponder = self.window.firstResponder
let prevFrame = self.window.frame
self.clearCustomTitle()
self.window.titleVisibility = .visible
self.internalSetRepUrl(url)
guard let contentView = self.window.contentView else {
return
}
self.window.titleVisibility = .hidden
self.window.styleMask.insert(.fullSizeContentView)
let title = NSTextField(forAutoLayout: ())
title.isEditable = false
title.isSelectable = false
title.isBordered = false
title.isBezeled = false
title.backgroundColor = .clear
title.textColor = self.theme.foreground
title.stringValue = self.window.title
contentView.addSubview(title)
title.autoPinEdge(toSuperviewEdge: .top, withInset: 3)
self.titleView = title
if let button = self.window.standardWindowButton(.documentIconButton) {
button.removeFromSuperview() // remove the rep icon from the original superview and add it to content view
contentView.addSubview(button)
button.autoSetDimension(.width, toSize: 16)
button.autoSetDimension(.height, toSize: 16)
button.autoPinEdge(toSuperviewEdge: .top, withInset: 3)
// Center the rep icon and the title side by side in the content view:
// rightView.left = leftView.right + gap
// rightView.right = parentView.centerX + (leftView.width + gap + rightView.width) / 2 - 4
// The (-4) at the end is an empirical value...
contentView.addConstraint(NSLayoutConstraint(item: title, attribute: .left,
relatedBy: .equal,
toItem: button, attribute: .right,
multiplier: 1,
constant: repIconToTitleGap))
contentView.addConstraint(
// Here we use title.intrinsicContentSize instead of title.frame because title.frame is still zero.
NSLayoutConstraint(
item: title, attribute: .right,
relatedBy: .equal,
toItem: contentView, attribute: .centerX,
multiplier: 1,
constant: -4 + (button.frame.width + repIconToTitleGap + title.intrinsicContentSize.width) / 2
)
)
self.repIcon = button
} else {
title.autoAlignAxis(toSuperviewAxis: .vertical)
}
self.window.setFrame(prevFrame, display: true, animate: false)
self.window.makeFirstResponder(prevFirstResponder)
}
fileprivate func clearCustomTitle() {
self.titleView?.removeFromSuperview()
self.repIcon?.removeFromSuperview()
self.titleView = nil
self.repIcon = nil
}
fileprivate func internalSetRepUrl(_ url: URL?) {
self.window.representedURL = nil
self.window.representedURL = url
}
}
fileprivate let repIconToTitleGap = CGFloat(4.0)

View File

@ -0,0 +1,208 @@
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
// MARK: - NeoVimViewDelegate
extension MainWindow {
func neoVimStopped() {
if self.isClosing {
return
}
self.isClosing = true
// If we close the window in the full screen mode, either by clicking the close button or by invoking :q
// the main thread crashes. We exit the full screen mode here as a quick and dirty hack.
if self.window.styleMask.contains(.fullScreen) {
self.window.toggleFullScreen(nil)
}
self.windowController.close()
self.set(dirtyStatus: false)
self.emit(self.uuidAction(for: .close))
if let cliPipePath = self.cliPipePath {
let fd = Darwin.open(cliPipePath, O_WRONLY)
guard fd != -1 else {
return
}
let handle = FileHandle(fileDescriptor: fd)
handle.closeFile()
_ = Darwin.close(fd)
}
}
func set(title: String) {
self.window.title = title
self.set(repUrl: self.window.representedURL, themed: self.titlebarThemed)
}
func set(dirtyStatus: Bool) {
self.emit(self.uuidAction(for: .setDirtyStatus(dirtyStatus)))
}
func cwdChanged() {
self.emit(self.uuidAction(for: .cd(to: self.neoVimView.cwd)))
}
func bufferListChanged() {
let buffers = self.neoVimView.allBuffers()
self.emit(self.uuidAction(for: .setBufferList(buffers)))
}
func currentBufferChanged(_ currentBuffer: NeoVimBuffer) {
self.emit(self.uuidAction(for: .setCurrentBuffer(currentBuffer)))
}
func tabChanged() {
guard let currentBuffer = self.neoVimView.currentBuffer() else {
return
}
self.currentBufferChanged(currentBuffer)
}
func colorschemeChanged(to neoVimTheme: NeoVimView.Theme) {
self.emit(uuidAction(for: .setTheme(Theme(neoVimTheme))))
}
func ipcBecameInvalid(reason: String) {
let alert = NSAlert()
alert.addButton(withTitle: "Close")
alert.messageText = "Sorry, an error occurred."
alert.informativeText = "VimR encountered an error from which it cannot recover. This window will now close.\n"
+ reason
alert.alertStyle = .critical
alert.beginSheetModal(for: self.window) { response in
self.windowController.close()
}
}
func scroll() {
self.scrollDebouncer.call(.scroll(to: Marked(self.neoVimView.currentPosition)))
}
func cursor(to position: Position) {
if position == self.editorPosition.payload {
return
}
self.editorPosition = Marked(position)
self.cursorDebouncer.call(.setCursor(to: self.editorPosition))
}
}
// MARK: - NSWindowDelegate
extension MainWindow {
func windowWillEnterFullScreen(_: Notification) {
self.unthemeTitlebar(dueFullScreen: true)
}
func windowDidExitFullScreen(_: Notification) {
if self.titlebarThemed {
self.themeTitlebar(grow: true)
}
}
func windowDidBecomeMain(_ notification: Notification) {
self.emit(self.uuidAction(for: .becomeKey(isFullScreen: self.window.styleMask.contains(.fullScreen))))
self.neoVimView.didBecomeMain()
}
func windowDidResignMain(_ notification: Notification) {
self.neoVimView.didResignMain()
}
func windowDidMove(_ notification: Notification) {
self.emit(self.uuidAction(for: .frameChanged(to: self.window.frame)))
}
func windowDidResize(_ notification: Notification) {
if self.window.styleMask.contains(.fullScreen) {
return
}
self.emit(self.uuidAction(for: .frameChanged(to: self.window.frame)))
}
func windowShouldClose(_: Any) -> Bool {
guard self.neoVimView.isCurrentBufferDirty() else {
self.neoVimView.closeCurrentTab()
return false
}
let alert = NSAlert()
alert.addButton(withTitle: "Cancel")
alert.addButton(withTitle: "Discard and Close")
alert.messageText = "The current buffer has unsaved changes!"
alert.alertStyle = .warning
alert.beginSheetModal(for: self.window, completionHandler: { response in
if response == NSAlertSecondButtonReturn {
self.neoVimView.closeCurrentTabWithoutSaving()
}
})
return false
}
}
// MARK: - WorkspaceDelegate
extension MainWindow {
func resizeWillStart(workspace: Workspace, tool: WorkspaceTool?) {
self.neoVimView.enterResizeMode()
}
func resizeDidEnd(workspace: Workspace, tool: WorkspaceTool?) {
self.neoVimView.exitResizeMode()
if let workspaceTool = tool, let toolIdentifier = self.toolIdentifier(for: workspaceTool) {
self.emit(self.uuidAction(for: .setState(for: toolIdentifier, with: workspaceTool)))
}
}
func toggled(tool: WorkspaceTool) {
if let toolIdentifier = self.toolIdentifier(for: tool) {
self.emit(self.uuidAction(for: .setState(for: toolIdentifier, with: tool)))
}
}
func moved(tool: WorkspaceTool) {
let tools = self.workspace.orderedTools.flatMap { (tool: WorkspaceTool) -> (Tools, WorkspaceTool)? in
guard let toolId = self.toolIdentifier(for: tool) else {
return nil
}
return (toolId, tool)
}
self.emit(self.uuidAction(for: .setToolsState(tools)))
}
fileprivate func toolIdentifier(for tool: WorkspaceTool) -> Tools? {
if tool == self.fileBrowserContainer {
return .fileBrowser
}
if tool == self.openedFileListContainer {
return .openedFilesList
}
if tool == self.previewContainer {
return .preview
}
if tool == self.htmlPreviewContainer {
return .htmlPreview
}
return nil
}
}

View File

@ -93,6 +93,35 @@ class MainWindow: NSObject,
}
let uuid: String
let emit: (UuidAction<Action>) -> Void
let windowController: NSWindowController
var window: NSWindow {
return self.windowController.window!
}
let workspace: Workspace
let neoVimView: NeoVimView
let scrollDebouncer = Debouncer<Action>(interval: 0.75)
let cursorDebouncer = Debouncer<Action>(interval: 0.75)
var editorPosition = Marked(Position.beginning)
let tools: [Tools: WorkspaceTool]
var previewContainer: WorkspaceTool?
var fileBrowserContainer: WorkspaceTool?
var openedFileListContainer: WorkspaceTool?
var htmlPreviewContainer: WorkspaceTool?
var theme = Theme.default
var titlebarThemed = false
var repIcon: NSButton?
var titleView: NSTextField?
var isClosing = false
let cliPipePath: String?
required init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType) {
self.emit = emitter.typedEmit()
@ -286,6 +315,10 @@ class MainWindow: NSObject,
self.window.makeFirstResponder(self.neoVimView)
}
func uuidAction(for action: Action) -> UuidAction<Action> {
return UuidAction(uuid: self.uuid, action: action)
}
func show() {
self.windowController.showWindow(self)
}
@ -304,31 +337,16 @@ class MainWindow: NSObject,
self.emit(uuidAction(for: .setTheme(theme)))
}
fileprivate let emit: (UuidAction<Action>) -> Void
fileprivate let disposeBag = DisposeBag()
fileprivate var currentBuffer: NeoVimBuffer?
fileprivate let windowController: NSWindowController
fileprivate var window: NSWindow {
return self.windowController.window!
}
fileprivate var defaultFont = NeoVimView.defaultFont
fileprivate var linespacing = NeoVimView.defaultLinespacing
fileprivate var usesLigatures = false
fileprivate let fontManager = NSFontManager.shared()
fileprivate let workspace: Workspace
fileprivate let neoVimView: NeoVimView
fileprivate var previewContainer: WorkspaceTool?
fileprivate var fileBrowserContainer: WorkspaceTool?
fileprivate var openedFileListContainer: WorkspaceTool?
fileprivate var htmlPreviewContainer: WorkspaceTool?
fileprivate var editorPosition = Marked(Position.beginning)
fileprivate var previewPosition = Marked(Position.beginning)
fileprivate var preview: PreviewTool?
@ -336,33 +354,15 @@ class MainWindow: NSObject,
fileprivate var fileBrowser: FileBrowser?
fileprivate var openedFileList: OpenedFileList?
fileprivate let tools: [Tools: WorkspaceTool]
fileprivate var usesTheme = true
fileprivate var lastThemeMark = Token()
fileprivate let scrollDebouncer = Debouncer<Action>(interval: 0.75)
fileprivate let cursorDebouncer = Debouncer<Action>(interval: 0.75)
fileprivate var isClosing = false
fileprivate let cliPipePath: String?
fileprivate var theme = Theme.default
fileprivate var titlebarThemed = false
fileprivate var repIcon: NSButton?
fileprivate var titleView: NSTextField?
fileprivate func updateNeoVimAppearance() {
self.neoVimView.font = self.defaultFont
self.neoVimView.linespacing = self.linespacing
self.neoVimView.usesLigatures = self.usesLigatures
}
fileprivate func uuidAction(for action: Action) -> UuidAction<Action> {
return UuidAction(uuid: self.uuid, action: action)
}
fileprivate func setWorkspaceTheme(with theme: Theme) {
var workspaceTheme = Workspace.Theme()
workspaceTheme.foreground = theme.foreground
@ -416,523 +416,3 @@ class MainWindow: NSObject,
self.workspace.autoPinEdgesToSuperviewEdges()
}
}
// MARK: - Custom title
extension MainWindow {
fileprivate func themeTitlebar(grow: Bool) {
if self.window.styleMask.contains(.fullScreen) {
return
}
let prevFirstResponder = self.window.firstResponder
self.window.titlebarAppearsTransparent = true
self.workspace.removeFromSuperview()
self.set(repUrl: self.window.representedURL, themed: true)
self.window.contentView?.addSubview(self.workspace)
self.workspace.autoPinEdge(toSuperviewEdge: .top, withInset: 22)
self.workspace.autoPinEdge(toSuperviewEdge: .right)
self.workspace.autoPinEdge(toSuperviewEdge: .bottom)
self.workspace.autoPinEdge(toSuperviewEdge: .left)
self.titlebarThemed = true
self.window.makeFirstResponder(prevFirstResponder)
}
fileprivate func unthemeTitlebar(dueFullScreen: Bool) {
self.clearCustomTitle()
guard let contentView = self.window.contentView else {
return
}
let prevFrame = window.frame
window.titlebarAppearsTransparent = false
self.workspace.removeFromSuperview()
self.window.titleVisibility = .visible
self.window.styleMask.remove(.fullSizeContentView)
self.set(repUrl: self.window.representedURL, themed: false)
contentView.addSubview(self.workspace)
self.workspace.autoPinEdgesToSuperviewEdges()
if !dueFullScreen {
self.window.setFrame(prevFrame, display: true, animate: false)
self.titlebarThemed = false
}
}
fileprivate func clearCustomTitle() {
self.titleView?.removeFromSuperview()
self.repIcon?.removeFromSuperview()
self.titleView = nil
self.repIcon = nil
}
fileprivate func internalSetRepUrl(_ url: URL?) {
self.window.representedURL = nil
self.window.representedURL = url
}
fileprivate func set(repUrl url: URL?, themed: Bool) {
if self.window.styleMask.contains(.fullScreen) || themed == false {
self.internalSetRepUrl(url)
return
}
let prevFirstResponder = self.window.firstResponder
let prevFrame = self.window.frame
self.clearCustomTitle()
self.window.titleVisibility = .visible
self.internalSetRepUrl(url)
guard let contentView = self.window.contentView else {
return
}
self.window.titleVisibility = .hidden
self.window.styleMask.insert(.fullSizeContentView)
let title = NSTextField(forAutoLayout: ())
title.isEditable = false
title.isSelectable = false
title.isBordered = false
title.isBezeled = false
title.backgroundColor = .clear
title.textColor = self.theme.foreground
title.stringValue = self.window.title
contentView.addSubview(title)
title.autoPinEdge(toSuperviewEdge: .top, withInset: 3)
self.titleView = title
if let button = self.window.standardWindowButton(.documentIconButton) {
button.removeFromSuperview() // remove the rep icon from the original superview and add it to content view
contentView.addSubview(button)
button.autoSetDimension(.width, toSize: 16)
button.autoSetDimension(.height, toSize: 16)
button.autoPinEdge(toSuperviewEdge: .top, withInset: 3)
// Center the rep icon and the title side by side in the content view:
// rightView.left = leftView.right + gap
// rightView.right = parentView.centerX + (leftView.width + gap + rightView.width) / 2 - 4
// The (-4) at the end is an empirical value...
contentView.addConstraint(NSLayoutConstraint(item: title, attribute: .left,
relatedBy: .equal,
toItem: button, attribute: .right,
multiplier: 1,
constant: repIconToTitleGap))
contentView.addConstraint(
// Here we use title.intrinsicContentSize instead of title.frame because title.frame is still zero.
NSLayoutConstraint(
item: title, attribute: .right,
relatedBy: .equal,
toItem: contentView, attribute: .centerX,
multiplier: 1,
constant: -4 + (button.frame.width + repIconToTitleGap + title.intrinsicContentSize.width) / 2
)
)
self.repIcon = button
} else {
title.autoAlignAxis(toSuperviewAxis: .vertical)
}
self.window.setFrame(prevFrame, display: true, animate: false)
self.window.makeFirstResponder(prevFirstResponder)
}
}
// MARK: - NeoVimViewDelegate
extension MainWindow {
func neoVimStopped() {
if self.isClosing {
return
}
self.isClosing = true
// If we close the window in the full screen mode, either by clicking the close button or by invoking :q
// the main thread crashes. We exit the full screen mode here as a quick and dirty hack.
if self.window.styleMask.contains(.fullScreen) {
self.window.toggleFullScreen(nil)
}
self.windowController.close()
self.set(dirtyStatus: false)
self.emit(self.uuidAction(for: .close))
if let cliPipePath = self.cliPipePath {
let fd = Darwin.open(cliPipePath, O_WRONLY)
guard fd != -1 else {
return
}
let handle = FileHandle(fileDescriptor: fd)
handle.closeFile()
_ = Darwin.close(fd)
}
}
func set(title: String) {
self.window.title = title
self.set(repUrl: self.window.representedURL, themed: self.titlebarThemed)
}
func set(dirtyStatus: Bool) {
self.emit(self.uuidAction(for: .setDirtyStatus(dirtyStatus)))
}
func cwdChanged() {
self.emit(self.uuidAction(for: .cd(to: self.neoVimView.cwd)))
}
func bufferListChanged() {
let buffers = self.neoVimView.allBuffers()
self.emit(self.uuidAction(for: .setBufferList(buffers)))
}
func currentBufferChanged(_ currentBuffer: NeoVimBuffer) {
self.emit(self.uuidAction(for: .setCurrentBuffer(currentBuffer)))
}
func tabChanged() {
guard let currentBuffer = self.neoVimView.currentBuffer() else {
return
}
self.currentBufferChanged(currentBuffer)
}
func colorschemeChanged(to neoVimTheme: NeoVimView.Theme) {
self.emit(uuidAction(for: .setTheme(Theme(neoVimTheme))))
}
func ipcBecameInvalid(reason: String) {
let alert = NSAlert()
alert.addButton(withTitle: "Close")
alert.messageText = "Sorry, an error occurred."
alert.informativeText = "VimR encountered an error from which it cannot recover. This window will now close.\n"
+ reason
alert.alertStyle = .critical
alert.beginSheetModal(for: self.window) { response in
self.windowController.close()
}
}
func scroll() {
self.scrollDebouncer.call(.scroll(to: Marked(self.neoVimView.currentPosition)))
}
func cursor(to position: Position) {
if position == self.editorPosition.payload {
return
}
self.editorPosition = Marked(position)
self.cursorDebouncer.call(.setCursor(to: self.editorPosition))
}
}
// MARK: - NSWindowDelegate
extension MainWindow {
func windowWillEnterFullScreen(_: Notification) {
self.unthemeTitlebar(dueFullScreen: true)
}
func windowDidExitFullScreen(_: Notification) {
if self.titlebarThemed {
self.themeTitlebar(grow: true)
}
}
func windowDidBecomeMain(_ notification: Notification) {
self.emit(self.uuidAction(for: .becomeKey(isFullScreen: self.window.styleMask.contains(.fullScreen))))
self.neoVimView.didBecomeMain()
}
func windowDidResignMain(_ notification: Notification) {
self.neoVimView.didResignMain()
}
func windowDidMove(_ notification: Notification) {
self.emit(self.uuidAction(for: .frameChanged(to: self.window.frame)))
}
func windowDidResize(_ notification: Notification) {
if self.window.styleMask.contains(.fullScreen) {
return
}
self.emit(self.uuidAction(for: .frameChanged(to: self.window.frame)))
}
func windowShouldClose(_: Any) -> Bool {
guard self.neoVimView.isCurrentBufferDirty() else {
self.neoVimView.closeCurrentTab()
return false
}
let alert = NSAlert()
alert.addButton(withTitle: "Cancel")
alert.addButton(withTitle: "Discard and Close")
alert.messageText = "The current buffer has unsaved changes!"
alert.alertStyle = .warning
alert.beginSheetModal(for: self.window, completionHandler: { response in
if response == NSAlertSecondButtonReturn {
self.neoVimView.closeCurrentTabWithoutSaving()
}
})
return false
}
}
// MARK: - File Menu Item Actions
extension MainWindow {
@IBAction func newTab(_ sender: Any?) {
self.neoVimView.newTab()
}
@IBAction func openDocument(_ sender: Any?) {
let panel = NSOpenPanel()
panel.canChooseDirectories = true
panel.allowsMultipleSelection = true
panel.beginSheetModal(for: self.window) { result in
guard result == NSFileHandlingPanelOKButton else {
return
}
let urls = panel.urls
if self.neoVimView.allBuffers().count == 1 {
let isTransient = self.neoVimView.allBuffers().first?.isTransient ?? false
if isTransient {
self.neoVimView.cwd = FileUtils.commonParent(of: urls)
}
}
self.neoVimView.open(urls: urls)
}
}
@IBAction func openQuickly(_ sender: Any?) {
self.emit(self.uuidAction(for: .openQuickly))
}
@IBAction func saveDocument(_ sender: Any?) {
guard let curBuf = self.neoVimView.currentBuffer() else {
return
}
if curBuf.url == nil {
self.savePanelSheet { self.neoVimView.saveCurrentTab(url: $0) }
return
}
self.neoVimView.saveCurrentTab()
}
@IBAction func saveDocumentAs(_ sender: Any?) {
if self.neoVimView.currentBuffer() == nil {
return
}
self.savePanelSheet { url in
self.neoVimView.saveCurrentTab(url: url)
if self.neoVimView.isCurrentBufferDirty() {
self.neoVimView.openInNewTab(urls: [url])
} else {
self.neoVimView.openInCurrentTab(url: url)
}
}
}
fileprivate func savePanelSheet(action: @escaping (URL) -> Void) {
let panel = NSSavePanel()
panel.beginSheetModal(for: self.window) { result in
guard result == NSFileHandlingPanelOKButton else {
return
}
let showAlert: () -> Void = {
let alert = NSAlert()
alert.addButton(withTitle: "OK")
alert.messageText = "Invalid File Name"
alert.informativeText = "The file name you have entered cannot be used. Please use a different name."
alert.alertStyle = .warning
alert.runModal()
}
guard let url = panel.url else {
showAlert()
return
}
action(url)
}
}
}
// MARK: - Tools Menu Item Actions
extension MainWindow {
@IBAction func toggleAllTools(_ sender: Any?) {
self.workspace.toggleAllTools()
self.focusNeoVimView(self)
self.emit(self.uuidAction(for: .toggleAllTools(self.workspace.isAllToolsVisible)))
}
@IBAction func toggleToolButtons(_ sender: Any?) {
self.workspace.toggleToolButtons()
self.emit(self.uuidAction(for: .toggleToolButtons(self.workspace.isToolButtonsVisible)))
}
@IBAction func toggleFileBrowser(_ sender: Any?) {
let fileBrowser = self.fileBrowserContainer
if fileBrowser?.isSelected == true {
if fileBrowser?.view.isFirstResponder == true {
fileBrowser?.toggle()
self.focusNeoVimView(self)
} else {
self.emit(self.uuidAction(for: .focus(.fileBrowser)))
}
return
}
fileBrowser?.toggle()
self.emit(self.uuidAction(for: .focus(.fileBrowser)))
}
@IBAction func focusNeoVimView(_: Any?) {
// self.window.makeFirstResponder(self.neoVimView)
self.emit(self.uuidAction(for: .focus(.neoVimView)))
}
}
// MARK: - WorkspaceDelegate
extension MainWindow {
func resizeWillStart(workspace: Workspace, tool: WorkspaceTool?) {
self.neoVimView.enterResizeMode()
}
func resizeDidEnd(workspace: Workspace, tool: WorkspaceTool?) {
self.neoVimView.exitResizeMode()
if let workspaceTool = tool, let toolIdentifier = self.toolIdentifier(for: workspaceTool) {
self.emit(self.uuidAction(for: .setState(for: toolIdentifier, with: workspaceTool)))
}
}
func toggled(tool: WorkspaceTool) {
if let toolIdentifier = self.toolIdentifier(for: tool) {
self.emit(self.uuidAction(for: .setState(for: toolIdentifier, with: tool)))
}
}
func moved(tool: WorkspaceTool) {
let tools = self.workspace.orderedTools.flatMap { (tool: WorkspaceTool) -> (Tools, WorkspaceTool)? in
guard let toolId = self.toolIdentifier(for: tool) else {
return nil
}
return (toolId, tool)
}
self.emit(self.uuidAction(for: .setToolsState(tools)))
}
fileprivate func toolIdentifier(for tool: WorkspaceTool) -> Tools? {
if tool == self.fileBrowserContainer {
return .fileBrowser
}
if tool == self.openedFileListContainer {
return .openedFilesList
}
if tool == self.previewContainer {
return .preview
}
if tool == self.htmlPreviewContainer {
return .htmlPreview
}
return nil
}
}
// MARK: - NSUserInterfaceValidationsProtocol
extension MainWindow {
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
let canSave = self.neoVimView.currentBuffer() != nil
let canSaveAs = canSave
let canOpen = canSave
let canOpenQuickly = canSave
let canFocusNeoVimView = self.window.firstResponder != self.neoVimView
let canToggleFileBrowser = self.tools.keys.contains(.fileBrowser)
let canToggleTools = !self.tools.isEmpty
guard let action = item.action else {
return true
}
switch action {
case #selector(toggleAllTools(_:)), #selector(toggleToolButtons(_:)):
return canToggleTools
case #selector(toggleFileBrowser(_:)):
return canToggleFileBrowser
case #selector(focusNeoVimView(_:)):
return canFocusNeoVimView
case #selector(openDocument(_:)):
return canOpen
case #selector(openQuickly(_:)):
return canOpenQuickly
case #selector(saveDocument(_:)):
return canSave
case #selector(saveDocumentAs(_:)):
return canSaveAs
default:
return true
}
}
}
fileprivate let repIconToTitleGap = CGFloat(4.0)