2017-01-22 16:22:05 +03:00
|
|
|
/**
|
|
|
|
* Tae Won Ha - http://taewon.de - @hataewon
|
|
|
|
* See LICENSE
|
|
|
|
*/
|
2017-01-17 21:47:59 +03:00
|
|
|
|
|
|
|
import Cocoa
|
|
|
|
import RxSwift
|
|
|
|
import SwiftNeoVim
|
|
|
|
import PureLayout
|
|
|
|
|
|
|
|
class MainWindow: NSObject,
|
|
|
|
UiComponent,
|
|
|
|
NeoVimViewDelegate,
|
2017-02-28 17:52:48 +03:00
|
|
|
NSWindowDelegate,
|
|
|
|
NSUserInterfaceValidations,
|
|
|
|
WorkspaceDelegate {
|
2017-01-17 21:47:59 +03:00
|
|
|
|
|
|
|
typealias StateType = State
|
|
|
|
|
|
|
|
enum Action {
|
|
|
|
|
2017-02-23 00:51:24 +03:00
|
|
|
case open(Set<Token>)
|
|
|
|
|
2017-01-17 21:47:59 +03:00
|
|
|
case cd(to: URL)
|
|
|
|
case setBufferList([NeoVimBuffer])
|
|
|
|
|
2017-01-22 16:22:05 +03:00
|
|
|
case setCurrentBuffer(NeoVimBuffer)
|
2017-02-26 12:26:37 +03:00
|
|
|
case setDirtyStatus(Bool)
|
2017-01-22 16:22:05 +03:00
|
|
|
|
2017-01-17 21:47:59 +03:00
|
|
|
case becomeKey
|
|
|
|
|
2017-02-11 20:32:22 +03:00
|
|
|
case scroll(to: Marked<Position>)
|
|
|
|
case setCursor(to: Marked<Position>)
|
2017-01-22 16:22:05 +03:00
|
|
|
|
2017-02-25 00:47:32 +03:00
|
|
|
case focus(FocusableView)
|
|
|
|
|
2017-02-19 20:00:41 +03:00
|
|
|
case openQuickly
|
|
|
|
|
2017-02-28 17:52:48 +03:00
|
|
|
case toggleAllTools(Bool)
|
|
|
|
case toggleToolButtons(Bool)
|
|
|
|
case setState(for: Tools, with: WorkspaceTool)
|
|
|
|
|
2017-01-17 21:47:59 +03:00
|
|
|
case close
|
|
|
|
}
|
|
|
|
|
2017-02-25 00:47:32 +03:00
|
|
|
enum FocusableView {
|
|
|
|
|
|
|
|
case neoVimView
|
|
|
|
case fileBrowser
|
|
|
|
case preview
|
|
|
|
}
|
|
|
|
|
2017-02-28 17:52:48 +03:00
|
|
|
enum Tools: String {
|
|
|
|
|
2017-03-01 00:04:12 +03:00
|
|
|
static let all = Set([Tools.fileBrowser, Tools.openedFilesList, Tools.preview])
|
|
|
|
|
2017-02-28 17:52:48 +03:00
|
|
|
case fileBrowser = "com.qvacua.vimr.tools.file-browser"
|
|
|
|
case openedFilesList = "com.qvacua.vimr.tools.opened-files-list"
|
|
|
|
case preview = "com.qvacua.vimr.tools.preview"
|
|
|
|
}
|
|
|
|
|
2017-01-17 21:47:59 +03:00
|
|
|
enum OpenMode {
|
|
|
|
|
|
|
|
case `default`
|
|
|
|
case currentTab
|
|
|
|
case newTab
|
|
|
|
case horizontalSplit
|
|
|
|
case verticalSplit
|
|
|
|
}
|
|
|
|
|
2017-01-25 00:39:19 +03:00
|
|
|
required init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType) {
|
2017-01-17 21:47:59 +03:00
|
|
|
self.uuid = state.uuid
|
|
|
|
self.emitter = emitter
|
|
|
|
|
2017-02-28 11:53:27 +03:00
|
|
|
self.defaultFont = state.appearance.font
|
|
|
|
self.linespacing = state.appearance.linespacing
|
|
|
|
self.usesLigatures = state.appearance.usesLigatures
|
2017-02-27 19:35:38 +03:00
|
|
|
|
2017-02-11 20:32:22 +03:00
|
|
|
self.editorPosition = state.preview.editorPosition
|
|
|
|
self.previewPosition = state.preview.previewPosition
|
|
|
|
|
2017-01-17 21:47:59 +03:00
|
|
|
self.neoVimView = NeoVimView(frame: CGRect.zero,
|
2017-02-28 13:10:04 +03:00
|
|
|
config: NeoVimView.Config(useInteractiveZsh: state.useInteractiveZsh))
|
2017-01-17 21:47:59 +03:00
|
|
|
self.neoVimView.configureForAutoLayout()
|
|
|
|
|
|
|
|
self.workspace = Workspace(mainView: self.neoVimView)
|
2017-02-06 00:39:55 +03:00
|
|
|
self.preview = PreviewTool(source: source, emitter: emitter, state: state)
|
2017-02-24 01:39:31 +03:00
|
|
|
self.fileBrowser = FileBrowser(source: source, emitter: emitter, state: state)
|
2017-02-26 14:00:19 +03:00
|
|
|
self.openedFileList = OpenedFileList(source: source, emitter: emitter, state: state)
|
2017-01-17 21:47:59 +03:00
|
|
|
|
|
|
|
self.windowController = NSWindowController(windowNibName: "MainWindow")
|
|
|
|
|
2017-02-25 00:47:32 +03:00
|
|
|
let previewConfig = WorkspaceTool.Config(title: "Preview",
|
|
|
|
view: self.preview,
|
|
|
|
customMenuItems: self.preview.menuItems)
|
|
|
|
self.previewContainer = WorkspaceTool(previewConfig)
|
2017-02-28 17:52:48 +03:00
|
|
|
previewContainer.dimension = state.tools[.preview]?.dimension ?? 250
|
2017-02-25 00:47:32 +03:00
|
|
|
|
|
|
|
let fileBrowserConfig = WorkspaceTool.Config(title: "Files",
|
|
|
|
view: self.fileBrowser,
|
|
|
|
customToolbar: self.fileBrowser.innerCustomToolbar,
|
|
|
|
customMenuItems: self.fileBrowser.menuItems)
|
|
|
|
self.fileBrowserContainer = WorkspaceTool(fileBrowserConfig)
|
2017-02-28 17:52:48 +03:00
|
|
|
fileBrowserContainer.dimension = state.tools[.fileBrowser]?.dimension ?? 200
|
2017-02-25 00:47:32 +03:00
|
|
|
|
2017-03-27 00:45:03 +03:00
|
|
|
let openedFileListConfig = WorkspaceTool.Config(title: "Buffers", view: self.openedFileList)
|
2017-02-26 14:00:19 +03:00
|
|
|
self.openedFileListContainer = WorkspaceTool(openedFileListConfig)
|
2017-02-28 17:52:48 +03:00
|
|
|
self.openedFileListContainer.dimension = state.tools[.openedFilesList]?.dimension ?? 200
|
2017-02-26 14:00:19 +03:00
|
|
|
|
2017-02-28 17:52:48 +03:00
|
|
|
self.workspace.append(tool: previewContainer, location: state.tools[.preview]?.location ?? .right)
|
|
|
|
self.workspace.append(tool: fileBrowserContainer, location: state.tools[.fileBrowser]?.location ?? .left)
|
|
|
|
self.workspace.append(tool: openedFileListContainer, location: state.tools[.openedFilesList]?.location ?? .left)
|
2017-02-25 00:47:32 +03:00
|
|
|
|
2017-03-01 18:59:23 +03:00
|
|
|
self.tools = [
|
|
|
|
.fileBrowser: self.fileBrowserContainer,
|
|
|
|
.openedFilesList: self.openedFileListContainer,
|
|
|
|
.preview: self.previewContainer,
|
|
|
|
]
|
2017-02-25 00:47:32 +03:00
|
|
|
|
2017-01-17 21:47:59 +03:00
|
|
|
super.init()
|
2017-01-22 16:22:05 +03:00
|
|
|
|
2017-03-01 18:59:23 +03:00
|
|
|
self.tools.forEach {
|
|
|
|
if state.tools[$0]?.open == true {
|
|
|
|
$1.toggle()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-28 17:52:48 +03:00
|
|
|
self.workspace.delegate = self
|
|
|
|
|
2017-02-12 19:28:49 +03:00
|
|
|
Observable
|
|
|
|
.of(self.scrollDebouncer.observable, self.cursorDebouncer.observable)
|
|
|
|
.merge()
|
2017-01-22 16:22:05 +03:00
|
|
|
.subscribe(onNext: { [unowned self] action in
|
|
|
|
self.emitter.emit(self.uuidAction(for: action))
|
|
|
|
})
|
|
|
|
.addDisposableTo(self.disposeBag)
|
|
|
|
|
2017-01-17 21:47:59 +03:00
|
|
|
self.addViews()
|
|
|
|
|
|
|
|
self.windowController.window?.delegate = self
|
|
|
|
|
|
|
|
source
|
|
|
|
.observeOn(MainScheduler.instance)
|
2017-02-12 12:53:24 +03:00
|
|
|
.subscribe(
|
|
|
|
onNext: { [unowned self] state in
|
2017-02-25 00:47:32 +03:00
|
|
|
if case .neoVimView = state.focusedView {
|
|
|
|
self.window.makeFirstResponder(self.neoVimView)
|
|
|
|
}
|
|
|
|
|
2017-02-26 12:26:37 +03:00
|
|
|
self.windowController.setDocumentEdited(state.isDirty)
|
|
|
|
|
2017-02-26 12:55:07 +03:00
|
|
|
if self.neoVimView.cwd != state.cwd {
|
|
|
|
self.neoVimView.cwd = state.cwd
|
|
|
|
}
|
|
|
|
|
2017-02-12 12:53:24 +03:00
|
|
|
if state.previewTool.isReverseSearchAutomatically
|
2017-02-12 19:28:49 +03:00
|
|
|
&& state.preview.previewPosition.hasDifferentMark(as: self.previewPosition) {
|
2017-02-12 12:53:24 +03:00
|
|
|
self.neoVimView.cursorGo(to: state.preview.previewPosition.payload)
|
2017-02-12 19:07:56 +03:00
|
|
|
} else if state.preview.forceNextReverse {
|
2017-02-12 18:40:49 +03:00
|
|
|
self.neoVimView.cursorGo(to: state.preview.previewPosition.payload)
|
|
|
|
}
|
|
|
|
|
2017-02-12 12:53:24 +03:00
|
|
|
self.previewPosition = state.preview.previewPosition
|
2017-02-23 00:51:24 +03:00
|
|
|
|
|
|
|
self.marksForOpenedUrls.subtracting(state.urlsToOpen.map { $0.mark }).forEach {
|
|
|
|
self.marksForOpenedUrls.remove($0)
|
|
|
|
}
|
|
|
|
|
|
|
|
self.open(markedUrls: state.urlsToOpen)
|
2017-02-26 16:23:23 +03:00
|
|
|
|
|
|
|
if self.currentBuffer != state.currentBuffer {
|
|
|
|
self.currentBuffer = state.currentBuffer
|
|
|
|
if let currentBuffer = self.currentBuffer {
|
|
|
|
self.neoVimView.select(buffer: currentBuffer)
|
|
|
|
}
|
|
|
|
}
|
2017-02-28 11:53:27 +03:00
|
|
|
|
|
|
|
if self.defaultFont != state.appearance.font
|
|
|
|
|| self.linespacing != state.appearance.linespacing
|
|
|
|
|| self.usesLigatures != state.appearance.usesLigatures {
|
|
|
|
self.defaultFont = state.appearance.font
|
|
|
|
self.linespacing = state.appearance.linespacing
|
|
|
|
self.usesLigatures = state.appearance.usesLigatures
|
|
|
|
|
|
|
|
self.updateNeoVimAppearance()
|
|
|
|
}
|
2017-02-12 12:53:24 +03:00
|
|
|
},
|
|
|
|
onCompleted: {
|
|
|
|
self.windowController.close()
|
|
|
|
})
|
2017-01-17 21:47:59 +03:00
|
|
|
.addDisposableTo(self.disposeBag)
|
|
|
|
|
2017-02-28 11:53:27 +03:00
|
|
|
self.updateNeoVimAppearance()
|
|
|
|
self.neoVimView.delegate = self
|
|
|
|
if self.neoVimView.cwd != state.cwd {
|
2017-01-17 21:47:59 +03:00
|
|
|
self.neoVimView.cwd = state.cwd
|
|
|
|
}
|
|
|
|
|
2017-02-23 00:51:24 +03:00
|
|
|
self.open(markedUrls: state.urlsToOpen)
|
|
|
|
|
2017-02-28 11:53:27 +03:00
|
|
|
self.window.makeFirstResponder(self.neoVimView)
|
2017-02-23 00:51:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func show() {
|
|
|
|
self.windowController.showWindow(self)
|
|
|
|
}
|
|
|
|
|
|
|
|
func closeAllNeoVimWindowsWithoutSaving() {
|
|
|
|
self.neoVimView.closeAllWindowsWithoutSaving()
|
|
|
|
}
|
|
|
|
|
2017-02-24 01:39:31 +03:00
|
|
|
fileprivate let emitter: ActionEmitter
|
|
|
|
fileprivate let disposeBag = DisposeBag()
|
|
|
|
|
|
|
|
fileprivate let uuid: String
|
|
|
|
|
2017-02-26 16:23:23 +03:00
|
|
|
fileprivate var currentBuffer: NeoVimBuffer?
|
|
|
|
|
2017-02-24 01:39:31 +03:00
|
|
|
fileprivate let windowController: NSWindowController
|
|
|
|
fileprivate var window: NSWindow { return self.windowController.window! }
|
|
|
|
|
2017-02-28 11:53:27 +03:00
|
|
|
fileprivate var defaultFont: NSFont
|
|
|
|
fileprivate var linespacing: CGFloat
|
|
|
|
fileprivate var usesLigatures: Bool
|
|
|
|
|
2017-02-27 19:35:38 +03:00
|
|
|
fileprivate let fontManager = NSFontManager.shared()
|
|
|
|
|
2017-02-24 01:39:31 +03:00
|
|
|
fileprivate let workspace: Workspace
|
|
|
|
fileprivate let neoVimView: NeoVimView
|
|
|
|
|
2017-02-25 00:47:32 +03:00
|
|
|
fileprivate let previewContainer: WorkspaceTool
|
|
|
|
fileprivate let fileBrowserContainer: WorkspaceTool
|
2017-02-26 14:00:19 +03:00
|
|
|
fileprivate let openedFileListContainer: WorkspaceTool
|
2017-02-25 00:47:32 +03:00
|
|
|
|
2017-02-24 01:39:31 +03:00
|
|
|
fileprivate var editorPosition: Marked<Position>
|
|
|
|
fileprivate var previewPosition: Marked<Position>
|
|
|
|
|
2017-03-01 18:59:23 +03:00
|
|
|
fileprivate let preview: PreviewTool
|
2017-02-24 01:39:31 +03:00
|
|
|
fileprivate let fileBrowser: FileBrowser
|
2017-02-26 14:00:19 +03:00
|
|
|
fileprivate let openedFileList: OpenedFileList
|
|
|
|
|
2017-03-01 18:59:23 +03:00
|
|
|
fileprivate let tools: [Tools: WorkspaceTool]
|
|
|
|
|
2017-02-24 01:39:31 +03:00
|
|
|
fileprivate let scrollDebouncer = Debouncer<Action>(interval: 0.75)
|
|
|
|
fileprivate let cursorDebouncer = Debouncer<Action>(interval: 0.75)
|
|
|
|
|
|
|
|
fileprivate var marksForOpenedUrls = Set<Token>()
|
|
|
|
|
2017-02-28 11:53:27 +03:00
|
|
|
fileprivate func updateNeoVimAppearance() {
|
|
|
|
self.neoVimView.font = self.defaultFont
|
|
|
|
self.neoVimView.linespacing = self.linespacing
|
|
|
|
self.neoVimView.usesLigatures = self.usesLigatures
|
|
|
|
}
|
|
|
|
|
2017-02-24 01:39:31 +03:00
|
|
|
fileprivate func uuidAction(for action: Action) -> UuidAction<Action> {
|
|
|
|
return UuidAction(uuid: self.uuid, action: action)
|
|
|
|
}
|
|
|
|
|
2017-02-23 00:51:24 +03:00
|
|
|
fileprivate func open(markedUrls: [Marked<[URL: OpenMode]>]) {
|
|
|
|
let markedUrlsToOpen = markedUrls.filter { !self.marksForOpenedUrls.contains($0.mark) }
|
|
|
|
|
|
|
|
markedUrls.map { $0.mark }.forEach {
|
|
|
|
self.marksForOpenedUrls.insert($0)
|
|
|
|
}
|
|
|
|
|
|
|
|
guard markedUrlsToOpen.count > 0 else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-01-17 21:47:59 +03:00
|
|
|
// If we don't call the following in the next tick, only half of the existing swap file warning is displayed.
|
|
|
|
// Dunno why...
|
|
|
|
DispatchUtils.gui {
|
2017-02-23 00:51:24 +03:00
|
|
|
markedUrlsToOpen.forEach { marked in
|
|
|
|
marked.payload.forEach { (url: URL, openMode: OpenMode) in
|
|
|
|
switch openMode {
|
2017-01-17 21:47:59 +03:00
|
|
|
|
2017-02-23 00:51:24 +03:00
|
|
|
case .default:
|
|
|
|
self.neoVimView.open(urls: [url])
|
2017-01-17 21:47:59 +03:00
|
|
|
|
2017-02-23 00:51:24 +03:00
|
|
|
case .currentTab:
|
|
|
|
self.neoVimView.openInCurrentTab(url: url)
|
2017-01-17 21:47:59 +03:00
|
|
|
|
2017-02-23 00:51:24 +03:00
|
|
|
case .newTab:
|
|
|
|
self.neoVimView.openInNewTab(urls: [url])
|
2017-01-17 21:47:59 +03:00
|
|
|
|
2017-02-23 00:51:24 +03:00
|
|
|
case .horizontalSplit:
|
|
|
|
self.neoVimView.openInHorizontalSplit(urls: [url])
|
2017-01-17 21:47:59 +03:00
|
|
|
|
2017-02-23 00:51:24 +03:00
|
|
|
case .verticalSplit:
|
|
|
|
self.neoVimView.openInVerticalSplit(urls: [url])
|
2017-01-17 21:47:59 +03:00
|
|
|
|
2017-02-23 00:51:24 +03:00
|
|
|
}
|
2017-01-17 21:47:59 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-23 00:51:24 +03:00
|
|
|
// not good, but we need it because we don't want to re-build the whole tab/window/buffer state of neovim in
|
|
|
|
// MainWindow.State
|
|
|
|
self.emitter.emit(self.uuidAction(for: Action.open(Set(markedUrls.map { $0.mark }))))
|
|
|
|
}
|
2017-02-04 17:34:13 +03:00
|
|
|
}
|
|
|
|
|
2017-01-17 21:47:59 +03:00
|
|
|
fileprivate func addViews() {
|
|
|
|
let contentView = self.window.contentView!
|
|
|
|
|
|
|
|
contentView.addSubview(self.workspace)
|
|
|
|
|
|
|
|
self.workspace.autoPinEdgesToSuperviewEdges()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - NeoVimViewDelegate
|
|
|
|
extension MainWindow {
|
|
|
|
|
|
|
|
func neoVimStopped() {
|
2017-02-12 12:53:24 +03:00
|
|
|
self.emitter.emit(self.uuidAction(for: .close))
|
2017-01-17 21:47:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func set(title: String) {
|
|
|
|
self.window.title = title
|
|
|
|
}
|
|
|
|
|
|
|
|
func set(dirtyStatus: Bool) {
|
2017-02-26 12:26:37 +03:00
|
|
|
self.emitter.emit(self.uuidAction(for: .setDirtyStatus(dirtyStatus)))
|
2017-01-17 21:47:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func cwdChanged() {
|
2017-01-22 16:22:05 +03:00
|
|
|
self.emitter.emit(self.uuidAction(for: .cd(to: self.neoVimView.cwd)))
|
2017-01-17 21:47:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func bufferListChanged() {
|
|
|
|
let buffers = self.neoVimView.allBuffers()
|
2017-01-22 16:22:05 +03:00
|
|
|
self.emitter.emit(self.uuidAction(for: .setBufferList(buffers)))
|
2017-01-17 21:47:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func currentBufferChanged(_ currentBuffer: NeoVimBuffer) {
|
2017-02-26 16:23:23 +03:00
|
|
|
if self.currentBuffer == currentBuffer {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-01-22 16:22:05 +03:00
|
|
|
self.emitter.emit(self.uuidAction(for: .setCurrentBuffer(currentBuffer)))
|
2017-02-26 16:23:23 +03:00
|
|
|
self.currentBuffer = currentBuffer
|
2017-01-17 21:47:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func tabChanged() {
|
2017-01-22 16:22:05 +03:00
|
|
|
guard let currentBuffer = self.neoVimView.currentBuffer() else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
self.currentBufferChanged(currentBuffer)
|
2017-01-17 21:47:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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() {
|
2017-02-11 20:32:22 +03:00
|
|
|
self.scrollDebouncer.call(.scroll(to: Marked(self.neoVimView.currentPosition)))
|
2017-01-17 21:47:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func cursor(to position: Position) {
|
2017-02-11 20:32:22 +03:00
|
|
|
if position == self.editorPosition.payload {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
self.editorPosition = Marked(position)
|
|
|
|
self.cursorDebouncer.call(.setCursor(to: self.editorPosition))
|
2017-01-17 21:47:59 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - NSWindowDelegate
|
|
|
|
extension MainWindow {
|
|
|
|
|
|
|
|
func windowDidBecomeKey(_: Notification) {
|
2017-01-22 16:22:05 +03:00
|
|
|
self.emitter.emit(self.uuidAction(for: .becomeKey))
|
2017-01-17 21:47:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2017-02-20 21:33:22 +03:00
|
|
|
|
2017-02-25 00:47:32 +03:00
|
|
|
// MARK: - File Menu Item Actions
|
2017-02-20 21:33:22 +03:00
|
|
|
extension MainWindow {
|
|
|
|
|
2017-02-25 00:47:32 +03:00
|
|
|
@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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-20 21:33:22 +03:00
|
|
|
@IBAction func openQuickly(_ sender: Any?) {
|
|
|
|
self.emitter.emit(self.uuidAction(for: .openQuickly))
|
|
|
|
}
|
2017-02-25 00:47:32 +03:00
|
|
|
|
|
|
|
@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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-27 19:35:38 +03:00
|
|
|
// MARK: - Font Menu Item Actions
|
|
|
|
extension MainWindow {
|
|
|
|
|
|
|
|
@IBAction func resetFontSize(_ sender: Any?) {
|
2017-02-28 11:53:27 +03:00
|
|
|
self.neoVimView.font = self.defaultFont
|
2017-02-27 19:35:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func makeFontBigger(_ sender: Any?) {
|
|
|
|
let curFont = self.neoVimView.font
|
|
|
|
let font = self.fontManager.convert(curFont, toSize: min(curFont.pointSize + 1, NeoVimView.maxFontSize))
|
|
|
|
self.neoVimView.font = font
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func makeFontSmaller(_ sender: Any?) {
|
|
|
|
let curFont = self.neoVimView.font
|
|
|
|
let font = self.fontManager.convert(curFont, toSize: max(curFont.pointSize - 1, NeoVimView.minFontSize))
|
|
|
|
self.neoVimView.font = font
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-25 00:47:32 +03:00
|
|
|
// MARK: - Tools Menu Item Actions
|
|
|
|
extension MainWindow {
|
|
|
|
|
|
|
|
@IBAction func toggleAllTools(_ sender: Any?) {
|
|
|
|
self.workspace.toggleAllTools()
|
|
|
|
self.focusNeoVimView(self)
|
2017-02-28 17:52:48 +03:00
|
|
|
|
|
|
|
self.emitter.emit(self.uuidAction(for: .toggleAllTools(self.workspace.isAllToolsVisible)))
|
2017-02-25 00:47:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func toggleToolButtons(_ sender: Any?) {
|
|
|
|
self.workspace.toggleToolButtons()
|
2017-02-28 17:52:48 +03:00
|
|
|
self.emitter.emit(self.uuidAction(for: .toggleToolButtons(self.workspace.isToolButtonsVisible)))
|
2017-02-25 00:47:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func toggleFileBrowser(_ sender: Any?) {
|
|
|
|
let fileBrowser = self.fileBrowserContainer
|
|
|
|
|
|
|
|
if fileBrowser.isSelected {
|
|
|
|
if fileBrowser.view.isFirstResponder {
|
|
|
|
fileBrowser.toggle()
|
|
|
|
self.focusNeoVimView(self)
|
|
|
|
} else {
|
|
|
|
self.emitter.emit(self.uuidAction(for: .focus(.fileBrowser)))
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fileBrowser.toggle()
|
|
|
|
self.emitter.emit(self.uuidAction(for: .focus(.fileBrowser)))
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func focusNeoVimView(_: Any?) {
|
|
|
|
// self.window.makeFirstResponder(self.neoVimView)
|
|
|
|
self.emitter.emit(self.uuidAction(for: .focus(.neoVimView)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-28 17:52:48 +03:00
|
|
|
// 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.emitter.emit(self.uuidAction(for: .setState(for: toolIdentifier, with: workspaceTool)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toggled(tool: WorkspaceTool) {
|
|
|
|
if let toolIdentifier = self.toolIdentifier(for: tool) {
|
|
|
|
self.emitter.emit(self.uuidAction(for: .setState(for: toolIdentifier, with: tool)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func moved(tool: WorkspaceTool) {
|
|
|
|
if let toolIdentifier = self.toolIdentifier(for: tool) {
|
|
|
|
self.emitter.emit(self.uuidAction(for: .setState(for: toolIdentifier, with: tool)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fileprivate func toolIdentifier(for tool: WorkspaceTool) -> Tools? {
|
|
|
|
switch tool {
|
|
|
|
|
|
|
|
case self.fileBrowserContainer:
|
|
|
|
return .fileBrowser
|
|
|
|
|
|
|
|
case self.openedFileListContainer:
|
|
|
|
return .openedFilesList
|
|
|
|
|
|
|
|
case self.previewContainer:
|
|
|
|
return .preview
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-25 00:47:32 +03:00
|
|
|
// MARK: - NSUserInterfaceValidationsProtocol
|
|
|
|
extension MainWindow {
|
|
|
|
|
|
|
|
public 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
|
|
|
|
|
|
|
|
guard let action = item.action else {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
switch action {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2017-02-20 21:33:22 +03:00
|
|
|
}
|