mirror of
https://github.com/qvacua/vimr.git
synced 2024-11-24 11:37:32 +03:00
Merge remote-tracking branch 'origin/develop' into update-neovim
Conflicts: NeoVimServer/server_ui.m SwiftNeoVim/NeoVimView.swift and many others...
This commit is contained in:
commit
a7bc657514
1
.gitignore
vendored
1
.gitignore
vendored
@ -179,5 +179,6 @@ Temporary Items
|
||||
|
||||
# -------------------------------------------
|
||||
*.generated.h
|
||||
*.generated.m
|
||||
|
||||
.deps
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12120" systemVersion="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12120" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12120"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
@ -12,11 +12,11 @@
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="MacNeovim" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" animationBehavior="default" id="QvC-M9-y7g">
|
||||
<window title="MacNeovim" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" frameAutosaveName="neovim-window" animationBehavior="default" id="QvC-M9-y7g">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2304" height="1273"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
|
@ -8,7 +8,6 @@
|
||||
#import "Logging.h"
|
||||
#import "server_globals.h"
|
||||
#import "NeoVimServer.h"
|
||||
#import "NeoVimUiBridgeProtocol.h"
|
||||
#import "NeoVimBuffer.h"
|
||||
#import "NeoVimWindow.h"
|
||||
#import "NeoVimTab.h"
|
||||
@ -32,6 +31,28 @@
|
||||
|
||||
#define pun_type(t, x) (*((t *) (&(x))))
|
||||
|
||||
// From NeoVimUiBridgeProtocol.h
|
||||
typedef NS_ENUM(NSUInteger, FontTrait) {
|
||||
FontTraitNone = 0,
|
||||
FontTraitItalic = (1 << 0),
|
||||
FontTraitBold = (1 << 1),
|
||||
FontTraitUnderline = (1 << 2),
|
||||
FontTraitUndercurl = (1 << 3)
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
FontTrait fontTrait;
|
||||
|
||||
unsigned int foreground;
|
||||
unsigned int background;
|
||||
unsigned int special;
|
||||
} CellAttributes;
|
||||
|
||||
static NSInteger _default_foreground = 0xFF000000;
|
||||
static NSInteger _default_background = 0xFFFFFFFF;
|
||||
static NSInteger _default_special = 0xFFFF0000;
|
||||
// ---
|
||||
|
||||
typedef struct {
|
||||
UIBridgeData *bridge;
|
||||
Loop *loop;
|
||||
@ -42,9 +63,6 @@ typedef struct {
|
||||
// We declare nvim_main because it's not declared in any header files of neovim
|
||||
extern int nvim_main(int argc, char **argv);
|
||||
|
||||
static NSInteger _default_foreground = qDefaultForeground;
|
||||
static NSInteger _default_background = qDefaultBackground;
|
||||
static NSInteger _default_special = qDefaultSpecial;
|
||||
|
||||
// The thread in which neovim's main runs
|
||||
static uv_thread_t _nvim_thread;
|
||||
|
@ -20,7 +20,8 @@ extension CellAttributes: CustomStringConvertible, Equatable {
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "CellAttributes<fg: \(String(format: "%x", self.foreground)), bg: \(String(format: "%x", self.background)))"
|
||||
return "CellAttributes<fg: \(String(self.foreground, radix: 16)), " +
|
||||
"bg: \(String(self.background, radix: 16)))>"
|
||||
}
|
||||
|
||||
public var inverted: CellAttributes {
|
||||
|
163
SwiftNeoVim/Logger.swift
Normal file
163
SwiftNeoVim/Logger.swift
Normal file
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
class FileLogger {
|
||||
|
||||
enum Level: String {
|
||||
|
||||
case `default` = "DEFAULT"
|
||||
case info = "INFO"
|
||||
case debug = "DEBUG"
|
||||
case error = "ERROR"
|
||||
case fault = "FAULT"
|
||||
}
|
||||
|
||||
let uuid = UUID().uuidString
|
||||
let name: String
|
||||
|
||||
let shouldLogDebug: Bool
|
||||
|
||||
init<T>(as name: T, with fileUrl: URL) {
|
||||
#if DEBUG
|
||||
self.shouldLogDebug = true
|
||||
#else
|
||||
self.shouldLogDebug = false
|
||||
#endif
|
||||
|
||||
switch name {
|
||||
case let str as String: self.name = str
|
||||
default: self.name = String(describing: name)
|
||||
}
|
||||
|
||||
guard fileUrl.isFileURL else {
|
||||
preconditionFailure("\(fileUrl) must be a file URL!")
|
||||
}
|
||||
|
||||
self.queue = DispatchQueue(label: self.uuid, qos: .background)
|
||||
|
||||
self.fileUrl = fileUrl
|
||||
self.setupFileHandle(at: fileUrl)
|
||||
|
||||
self.logDateFormatter.dateFormat = "dd HH:mm:SSS"
|
||||
self.fileDateFormatter.dateFormat = "yyyy-MM-dd_HH-mm-SSS"
|
||||
}
|
||||
|
||||
fileprivate func setupFileHandle(at fileUrl: URL) {
|
||||
if !fileManager.fileExists(atPath: fileUrl.path) {
|
||||
fileManager.createFile(atPath: fileUrl.path, contents: nil)
|
||||
}
|
||||
|
||||
if let fileHandle = try? FileHandle(forWritingTo: fileUrl) {
|
||||
self.fileHandle = fileHandle
|
||||
self.fileHandle.seekToEndOfFile()
|
||||
} else {
|
||||
NSLog("[ERROR] Could not get handle for \(fileUrl), defaulting to STDOUT")
|
||||
self.fileHandle = FileHandle.standardOutput
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.fileHandle.closeFile()
|
||||
}
|
||||
|
||||
func mark(file: String = #file, line: Int = #line, function: String = #function) {
|
||||
guard self.shouldLogDebug else {
|
||||
return
|
||||
}
|
||||
|
||||
self.log("", level: .debug, file: file, line: line, function: function)
|
||||
}
|
||||
|
||||
func `default`<T>(_ message: @escaping @autoclosure () -> T,
|
||||
file: String = #file, line: Int = #line, function: String = #function) {
|
||||
|
||||
self.log(message, level: .default, file: file, line: line, function: function)
|
||||
}
|
||||
|
||||
func info<T>(_ message: @escaping @autoclosure () -> T,
|
||||
file: String = #file, line: Int = #line, function: String = #function) {
|
||||
|
||||
self.log(message, level: .info, file: file, line: line, function: function)
|
||||
}
|
||||
|
||||
func debug<T>(_ message: @escaping @autoclosure () -> T,
|
||||
file: String = #file, line: Int = #line, function: String = #function) {
|
||||
|
||||
guard self.shouldLogDebug else {
|
||||
return
|
||||
}
|
||||
|
||||
self.log(message, level: .debug, file: file, line: line, function: function)
|
||||
}
|
||||
|
||||
func error<T>(_ message: @escaping @autoclosure () -> T,
|
||||
file: String = #file, line: Int = #line, function: String = #function) {
|
||||
|
||||
self.log(message, level: .error, file: file, line: line, function: function)
|
||||
}
|
||||
|
||||
func fault<T>(_ message: @escaping @autoclosure () -> T,
|
||||
file: String = #file, line: Int = #line, function: String = #function) {
|
||||
|
||||
self.log(message, level: .fault, file: file, line: line, function: function)
|
||||
}
|
||||
|
||||
func log<T>(_ message: @escaping @autoclosure () -> T, level: Level = .default,
|
||||
file: String = #file, line: Int = #line, function: String = #function) {
|
||||
|
||||
self.queue.async {
|
||||
let timestamp = self.logDateFormatter.string(from: Date())
|
||||
let strMsg = self.string(from: message())
|
||||
|
||||
let logMsg = "\(timestamp) \(self.name) \(function) \(strMsg)"
|
||||
let data = "[\(level.rawValue)] \(logMsg)\n".data(using: .utf8) ?? conversionError
|
||||
|
||||
self.fileHandle.write(data)
|
||||
|
||||
if self.fileHandle.offsetInFile >= maxFileSize {
|
||||
self.archiveLogFile()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func archiveLogFile() {
|
||||
self.fileHandle.closeFile()
|
||||
|
||||
do {
|
||||
let fileTimestamp = self.fileDateFormatter.string(from: Date())
|
||||
let fileName = self.fileUrl.deletingPathExtension().lastPathComponent
|
||||
let archiveFileName = "\(fileName)-\(fileTimestamp).\(self.fileUrl.pathExtension)"
|
||||
let archiveFileUrl = self.fileUrl
|
||||
.deletingLastPathComponent().appendingPathComponent(archiveFileName)
|
||||
|
||||
try fileManager.moveItem(at: self.fileUrl, to: archiveFileUrl)
|
||||
} catch let error as NSError {
|
||||
NSLog("[ERROR] Could not archive log file: \(error)")
|
||||
}
|
||||
|
||||
self.setupFileHandle(at: self.fileUrl)
|
||||
}
|
||||
|
||||
fileprivate func string<T>(from obj: T) -> String {
|
||||
switch obj {
|
||||
case let str as String: return str
|
||||
case let convertible as CustomStringConvertible: return convertible.description
|
||||
case let convertible as CustomDebugStringConvertible: return convertible.debugDescription
|
||||
default: return String(describing: obj)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let fileUrl: URL
|
||||
fileprivate var fileHandle = FileHandle.standardOutput
|
||||
fileprivate let logDateFormatter = DateFormatter()
|
||||
fileprivate let fileDateFormatter = DateFormatter()
|
||||
fileprivate let queue: DispatchQueue
|
||||
}
|
||||
|
||||
fileprivate let conversionError = "[ERROR] Could not convert log msg to Data!".data(using: .utf8)!
|
||||
fileprivate let fileManager = FileManager.default
|
||||
fileprivate let maxFileSize: UInt64 = 1 * 1024 * 1024
|
@ -31,6 +31,8 @@ typedef NS_ENUM(NSUInteger, CursorModeShape) {
|
||||
CursorModeShapeCount = 18,
|
||||
};
|
||||
|
||||
extern NSString * __nonnull cursorModeShapeName(CursorModeShape mode);
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FontTrait) {
|
||||
FontTraitNone = 0,
|
||||
FontTraitItalic = (1 << 0),
|
||||
|
27
SwiftNeoVim/NeoVimUiBridgeProtocol.m
Normal file
27
SwiftNeoVim/NeoVimUiBridgeProtocol.m
Normal file
@ -0,0 +1,27 @@
|
||||
@import Foundation;
|
||||
#import "NeoVimUiBridgeProtocol.h"
|
||||
|
||||
extern NSString * __nonnull cursorModeShapeName(CursorModeShape mode) {
|
||||
switch (mode) {
|
||||
case CursorModeShapeNormal: return @"Normal";
|
||||
case CursorModeShapeVisual: return @"Visual";
|
||||
case CursorModeShapeInsert: return @"Insert";
|
||||
case CursorModeShapeReplace: return @"Replace";
|
||||
case CursorModeShapeCmdline: return @"Cmdline";
|
||||
case CursorModeShapeCmdlineInsert: return @"CmdlineInsert";
|
||||
case CursorModeShapeCmdlineReplace: return @"CmdlineReplace";
|
||||
case CursorModeShapeOperatorPending: return @"OperatorPending";
|
||||
case CursorModeShapeVisualExclusive: return @"VisualExclusive";
|
||||
case CursorModeShapeOnCmdline: return @"OnCmdline";
|
||||
case CursorModeShapeOnStatusLine: return @"OnStatusLine";
|
||||
case CursorModeShapeDraggingStatusLine: return @"DraggingStatusLine";
|
||||
case CursorModeShapeOnVerticalSepLine: return @"OnVerticalSepLine";
|
||||
case CursorModeShapeDraggingVerticalSepLine: return @"DraggingVerticalSepLine";
|
||||
case CursorModeShapeMore: return @"More";
|
||||
case CursorModeShapeMoreLastLine: return @"MoreLastLine";
|
||||
case CursorModeShapeShowingMatchingParen: return @"ShowingMatchingParen";
|
||||
case CursorModeShapeTermFocus: return @"TermFocus";
|
||||
case CursorModeShapeCount: return @"Count";
|
||||
default: return @"NON_EXISTING_MODE";
|
||||
}
|
||||
}
|
161
SwiftNeoVim/NeoVimView+Api.swift
Normal file
161
SwiftNeoVim/NeoVimView+Api.swift
Normal file
@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension NeoVimView {
|
||||
|
||||
public func syncNeoVimWithBounds() {
|
||||
self.resizeNeoVimUi(to: self.bounds.size)
|
||||
}
|
||||
|
||||
public func enterResizeMode() {
|
||||
self.currentlyResizing = true
|
||||
self.needsDisplay = true
|
||||
}
|
||||
|
||||
public func exitResizeMode() {
|
||||
self.currentlyResizing = false
|
||||
self.needsDisplay = true
|
||||
self.resizeNeoVimUi(to: self.bounds.size)
|
||||
}
|
||||
|
||||
/**
|
||||
- returns: nil when for exampls a quickfix panel is open.
|
||||
*/
|
||||
public func currentBuffer() -> NeoVimBuffer? {
|
||||
return self.agent.buffers().first { $0.isCurrent }
|
||||
}
|
||||
|
||||
public func allBuffers() -> [NeoVimBuffer] {
|
||||
return self.agent.tabs().map { $0.allBuffers() }.flatMap { $0 }
|
||||
}
|
||||
|
||||
public func hasDirtyDocs() -> Bool {
|
||||
return self.agent.hasDirtyDocs()
|
||||
}
|
||||
|
||||
public func isCurrentBufferDirty() -> Bool {
|
||||
let curBuf = self.currentBuffer()
|
||||
return curBuf?.isDirty ?? true
|
||||
}
|
||||
|
||||
public func newTab() {
|
||||
self.exec(command: "tabe")
|
||||
}
|
||||
|
||||
public func `open`(urls: [URL]) {
|
||||
let tabs = self.agent.tabs()
|
||||
let buffers = self.allBuffers()
|
||||
let currentBufferIsTransient = buffers.first { $0.isCurrent }?.isTransient ?? false
|
||||
|
||||
urls.enumerated().forEach { (idx, url) in
|
||||
if buffers.filter({ $0.url == url }).first != nil {
|
||||
for window in tabs.map({ $0.windows }).flatMap({ $0 }) {
|
||||
if window.buffer.url == url {
|
||||
self.agent.select(window)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if currentBufferIsTransient {
|
||||
self.open(url, cmd: "e")
|
||||
} else {
|
||||
self.open(url, cmd: "tabe")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func openInNewTab(urls: [URL]) {
|
||||
urls.forEach { self.open($0, cmd: "tabe") }
|
||||
}
|
||||
|
||||
public func openInCurrentTab(url: URL) {
|
||||
self.open(url, cmd: "e")
|
||||
}
|
||||
|
||||
public func openInHorizontalSplit(urls: [URL]) {
|
||||
urls.forEach { self.open($0, cmd: "sp") }
|
||||
}
|
||||
|
||||
public func openInVerticalSplit(urls: [URL]) {
|
||||
urls.forEach { self.open($0, cmd: "vsp") }
|
||||
}
|
||||
|
||||
public func select(buffer: NeoVimBuffer) {
|
||||
for window in self.agent.tabs().map({ $0.windows }).flatMap({ $0 }) {
|
||||
if window.buffer.handle == buffer.handle {
|
||||
self.agent.select(window)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func closeCurrentTab() {
|
||||
self.exec(command: "q")
|
||||
}
|
||||
|
||||
public func saveCurrentTab() {
|
||||
self.exec(command: "w")
|
||||
}
|
||||
|
||||
public func saveCurrentTab(url: URL) {
|
||||
let path = url.path
|
||||
guard let escapedFileName = self.agent.escapedFileName(path) else {
|
||||
self.logger.fault("Escaped file name returned nil.")
|
||||
return
|
||||
}
|
||||
|
||||
self.exec(command: "w \(escapedFileName)")
|
||||
}
|
||||
|
||||
public func closeCurrentTabWithoutSaving() {
|
||||
self.exec(command: "q!")
|
||||
}
|
||||
|
||||
public func closeAllWindows() {
|
||||
self.exec(command: "qa")
|
||||
}
|
||||
|
||||
public func closeAllWindowsWithoutSaving() {
|
||||
self.exec(command: "qa!")
|
||||
}
|
||||
|
||||
public func vimOutput(of command: String) -> String {
|
||||
return self.agent.vimCommandOutput(command) ?? ""
|
||||
}
|
||||
|
||||
public func cursorGo(to position: Position) {
|
||||
self.agent.cursorGo(toRow: Int32(position.row), column: Int32(position.column))
|
||||
}
|
||||
|
||||
/**
|
||||
Does the following
|
||||
- normal mode: `:command<CR>`
|
||||
- else: `:<Esc>:command<CR>`
|
||||
|
||||
We don't use NeoVimAgent.vimCommand because if we do for example "e /some/file"
|
||||
and its swap file already exists, then NeoVimServer spins and become unresponsive.
|
||||
*/
|
||||
fileprivate func exec(command cmd: String) {
|
||||
switch self.mode {
|
||||
case .normal:
|
||||
self.agent.vimInput(":\(cmd)<CR>")
|
||||
default:
|
||||
self.agent.vimInput("<Esc>:\(cmd)<CR>")
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func `open`(_ url: URL, cmd: String) {
|
||||
let path = url.path
|
||||
guard let escapedFileName = self.agent.escapedFileName(path) else {
|
||||
self.logger.fault("Escaped file name returned nil.")
|
||||
return
|
||||
}
|
||||
|
||||
self.exec(command: "\(cmd) \(escapedFileName)")
|
||||
}
|
||||
}
|
299
SwiftNeoVim/NeoVimView+Draw.swift
Normal file
299
SwiftNeoVim/NeoVimView+Draw.swift
Normal file
@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension NeoVimView {
|
||||
|
||||
override public func viewDidMoveToWindow() {
|
||||
self.window?.colorSpace = self.colorSpace
|
||||
}
|
||||
|
||||
override public func draw(_ dirtyUnionRect: NSRect) {
|
||||
guard self.grid.hasData else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.inLiveResize || self.currentlyResizing {
|
||||
NSColor.windowBackgroundColor.set()
|
||||
dirtyUnionRect.fill()
|
||||
|
||||
let boundsSize = self.bounds.size
|
||||
|
||||
let emojiSize = self.currentEmoji.size(withAttributes: self.emojiAttrs)
|
||||
let emojiX = (boundsSize.width - emojiSize.width) / 2
|
||||
let emojiY = (boundsSize.height - emojiSize.height) / 2
|
||||
|
||||
let discreteSize = self.discreteSize(size: boundsSize)
|
||||
let displayStr = "\(discreteSize.width) × \(discreteSize.height)"
|
||||
|
||||
let size = displayStr.size(withAttributes: self.resizeTextAttrs)
|
||||
let x = (boundsSize.width - size.width) / 2
|
||||
let y = emojiY - size.height
|
||||
|
||||
self.currentEmoji.draw(at: CGPoint(x: emojiX, y: emojiY), withAttributes: self.emojiAttrs)
|
||||
displayStr.draw(at: CGPoint(x: x, y: y), withAttributes: self.resizeTextAttrs)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// self.logger.debug("\(#function): \(dirtyUnionRect)")
|
||||
let context = NSGraphicsContext.current()!.cgContext
|
||||
|
||||
if self.isCurrentlyPinching {
|
||||
let interpolationQuality = context.interpolationQuality
|
||||
context.interpolationQuality = .none
|
||||
|
||||
let boundsSize = self.bounds.size
|
||||
let targetSize = CGSize(width: boundsSize.width * self.pinchTargetScale,
|
||||
height: boundsSize.height * self.pinchTargetScale)
|
||||
self.pinchBitmap?.draw(in: CGRect(origin: self.bounds.origin, size: targetSize),
|
||||
from: CGRect.zero,
|
||||
operation: .sourceOver,
|
||||
fraction: 1,
|
||||
respectFlipped: true,
|
||||
hints: nil)
|
||||
|
||||
context.interpolationQuality = interpolationQuality
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// When both anti-aliasing and font smoothing is turned on, then the "Use LCD font smoothing
|
||||
// when available" setting is used to render texts,
|
||||
// cf. chapter 11 from "Programming with Quartz".
|
||||
context.setShouldSmoothFonts(true);
|
||||
context.textMatrix = CGAffineTransform.identity;
|
||||
context.setTextDrawingMode(.fill);
|
||||
|
||||
let dirtyRects = self.rectsBeingDrawn()
|
||||
// self.logger.debug("\(dirtyRects)")
|
||||
|
||||
self.rowRunIntersecting(rects: dirtyRects).forEach { self.draw(rowRun: $0, context: context) }
|
||||
self.drawCursor(context: context)
|
||||
}
|
||||
|
||||
func randomEmoji() -> String {
|
||||
let idx = Int(arc4random_uniform(UInt32(NeoVimView.emojis.count)))
|
||||
guard let scalar = UnicodeScalar(NeoVimView.emojis[idx]) else {
|
||||
return "😎"
|
||||
}
|
||||
|
||||
return String(scalar)
|
||||
}
|
||||
|
||||
fileprivate func draw(rowRun rowFrag: RowRun, context: CGContext) {
|
||||
// For background drawing we don't filter out the put(0, 0)s:
|
||||
// in some cases only the put(0, 0)-cells should be redrawn.
|
||||
// => FIXME: probably we have to consider this also when drawing further down,
|
||||
// ie when the range starts with '0'...
|
||||
self.drawBackground(
|
||||
positions: rowFrag.range.map { self.pointInViewFor(row: rowFrag.row, column: $0) },
|
||||
background: rowFrag.attrs.background
|
||||
)
|
||||
|
||||
let positions = rowFrag.range
|
||||
// filter out the put(0, 0)s (after a wide character)
|
||||
.filter { self.grid.cells[rowFrag.row][$0].string.characters.count > 0 }
|
||||
.map { self.pointInViewFor(row: rowFrag.row, column: $0) }
|
||||
|
||||
if positions.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
let string = self.grid.cells[rowFrag.row][rowFrag.range].reduce("") { $0 + $1.string }
|
||||
let offset = self.drawer.baselineOffset
|
||||
let glyphPositions = positions.map { CGPoint(x: $0.x, y: $0.y + offset) }
|
||||
|
||||
self.drawer.draw(
|
||||
string,
|
||||
positions: UnsafeMutablePointer(mutating: glyphPositions), positionsCount: positions.count,
|
||||
highlightAttrs: rowFrag.attrs,
|
||||
context: context
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func cursorRegion() -> Region {
|
||||
let cursorPosition: Position
|
||||
if self.mode == .cmdline
|
||||
|| self.mode == .cmdlineInsert
|
||||
|| self.mode == .cmdlineReplace {
|
||||
cursorPosition = self.grid.putPosition
|
||||
} else {
|
||||
cursorPosition = self.grid.screenCursor
|
||||
}
|
||||
|
||||
let saneRow = max(0, min(cursorPosition.row, self.grid.size.height - 1))
|
||||
let saneColumn = max(0, min(cursorPosition.column, self.grid.size.width - 1))
|
||||
|
||||
var cursorRegion = Region(top: saneRow, bottom: saneRow, left: saneColumn, right: saneColumn)
|
||||
|
||||
if self.grid.isNextCellEmpty(cursorPosition) {
|
||||
cursorRegion = Region(top: cursorPosition.row,
|
||||
bottom: cursorPosition.row,
|
||||
left: cursorPosition.column,
|
||||
right: min(self.grid.size.width - 1, cursorPosition.column + 1))
|
||||
}
|
||||
|
||||
return cursorRegion
|
||||
}
|
||||
|
||||
fileprivate func drawCursor(context: CGContext) {
|
||||
let cursorRegion = self.cursorRegion()
|
||||
let cursorRow = cursorRegion.top
|
||||
let cursorColumnStart = cursorRegion.left
|
||||
|
||||
if self.mode == .insert {
|
||||
ColorUtils.colorIgnoringAlpha(self.grid.foreground).withAlphaComponent(0.75).set()
|
||||
var cursorRect = self.cellRectFor(row: cursorRow, column: cursorColumnStart)
|
||||
cursorRect.size.width = 2
|
||||
cursorRect.fill()
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: for now do some rudimentary cursor drawing
|
||||
let attrsAtCursor = self.grid.cells[cursorRow][cursorColumnStart].attrs
|
||||
let attrs = CellAttributes(fontTrait: attrsAtCursor.fontTrait,
|
||||
foreground: self.grid.background,
|
||||
background: self.grid.foreground,
|
||||
special: self.grid.special)
|
||||
|
||||
// FIXME: take ligatures into account (is it a good idea to do this?)
|
||||
let rowRun = RowRun(row: cursorRegion.top, range: cursorRegion.columnRange, attrs: attrs)
|
||||
self.draw(rowRun: rowRun, context: context)
|
||||
}
|
||||
|
||||
fileprivate func drawBackground(positions: [CGPoint], background: Int) {
|
||||
ColorUtils.colorIgnoringAlpha(background).set()
|
||||
|
||||
// To use random color use the following
|
||||
// NSColor(calibratedRed: CGFloat(drand48()),
|
||||
// green: CGFloat(drand48()),
|
||||
// blue: CGFloat(drand48()),
|
||||
// alpha: 1.0).set()
|
||||
|
||||
let backgroundRect = CGRect(
|
||||
x: positions[0].x, y: positions[0].y,
|
||||
width: CGFloat(positions.count) * self.cellSize.width, height: self.cellSize.height
|
||||
)
|
||||
backgroundRect.fill()
|
||||
}
|
||||
|
||||
fileprivate func rowRunIntersecting(rects: [CGRect]) -> [RowRun] {
|
||||
return rects
|
||||
.map { rect -> (CountableClosedRange<Int>, CountableClosedRange<Int>) in
|
||||
// Get all Regions that intersects with the given rects.
|
||||
// There can be overlaps between the Regions, but for the time being we ignore them;
|
||||
// probably not necessary to optimize them away.
|
||||
let region = self.regionFor(rect: rect)
|
||||
return (region.rowRange, region.columnRange)
|
||||
}
|
||||
// All RowRuns for all Regions grouped by their row range.
|
||||
.map { self.rowRunsFor(rowRange: $0, columnRange: $1) }
|
||||
// Flattened RowRuns for all Regions.
|
||||
.flatMap { $0 }
|
||||
}
|
||||
|
||||
fileprivate func rowRunsFor(rowRange: CountableClosedRange<Int>,
|
||||
columnRange: CountableClosedRange<Int>) -> [RowRun] {
|
||||
|
||||
return rowRange
|
||||
.map { (row) -> [RowRun] in
|
||||
let rowCells = self.grid.cells[row]
|
||||
let startIdx = columnRange.lowerBound
|
||||
|
||||
var result = [RowRun(row: row, range: startIdx...startIdx, attrs: rowCells[startIdx].attrs)]
|
||||
columnRange.forEach { idx in
|
||||
if rowCells[idx].attrs == result.last!.attrs {
|
||||
let last = result.popLast()!
|
||||
result.append(RowRun(row: row, range: last.range.lowerBound...idx, attrs: last.attrs))
|
||||
} else {
|
||||
result.append(RowRun(row: row, range: idx...idx, attrs: rowCells[idx].attrs))
|
||||
}
|
||||
}
|
||||
|
||||
return result // All RowRuns for a row in a Region.
|
||||
} // All RowRuns for all rows in a Region grouped by row.
|
||||
.flatMap { $0 } // Flattened RowRuns for a Region.
|
||||
}
|
||||
|
||||
fileprivate func regionFor(rect: CGRect) -> Region {
|
||||
let cellWidth = self.cellSize.width
|
||||
let cellHeight = self.cellSize.height
|
||||
|
||||
let rowStart = max(
|
||||
Int(floor(
|
||||
(self.bounds.height - self.yOffset - (rect.origin.y + rect.size.height)) / cellHeight)
|
||||
), 0
|
||||
)
|
||||
let rowEnd = min(
|
||||
Int(ceil((self.bounds.height - self.yOffset - rect.origin.y) / cellHeight)) - 1,
|
||||
self.grid.size.height - 1
|
||||
)
|
||||
let columnStart = max(
|
||||
Int(floor((rect.origin.x - self.xOffset) / cellWidth)), 0
|
||||
)
|
||||
let columnEnd = min(
|
||||
Int(ceil((rect.origin.x - self.xOffset + rect.size.width) / cellWidth)) - 1,
|
||||
self.grid.size.width - 1
|
||||
)
|
||||
|
||||
return Region(top: rowStart, bottom: rowEnd, left: columnStart, right: columnEnd)
|
||||
}
|
||||
|
||||
fileprivate func pointInViewFor(position: Position) -> CGPoint {
|
||||
return self.pointInViewFor(row: position.row, column: position.column)
|
||||
}
|
||||
|
||||
fileprivate func pointInViewFor(row: Int, column: Int) -> CGPoint {
|
||||
return CGPoint(
|
||||
x: self.xOffset + CGFloat(column) * self.cellSize.width,
|
||||
y: self.bounds.size.height - self.yOffset - CGFloat(row) * self.cellSize.height
|
||||
- self.cellSize.height
|
||||
)
|
||||
}
|
||||
|
||||
func cellRectFor(row: Int, column: Int) -> CGRect {
|
||||
return CGRect(origin: self.pointInViewFor(row: row, column: column), size: self.cellSize)
|
||||
}
|
||||
|
||||
func regionRectFor(region: Region) -> CGRect {
|
||||
let top = CGFloat(region.top)
|
||||
let bottom = CGFloat(region.bottom)
|
||||
let left = CGFloat(region.left)
|
||||
let right = CGFloat(region.right)
|
||||
|
||||
let width = right - left + 1
|
||||
let height = bottom - top + 1
|
||||
|
||||
let cellWidth = self.cellSize.width
|
||||
let cellHeight = self.cellSize.height
|
||||
|
||||
return CGRect(
|
||||
x: self.xOffset + left * cellWidth,
|
||||
y: self.bounds.size.height - self.yOffset - top * cellHeight - height * cellHeight,
|
||||
width: width * cellWidth,
|
||||
height: height * cellHeight
|
||||
)
|
||||
}
|
||||
|
||||
func wrapNamedKeys(_ string: String) -> String {
|
||||
return "<\(string)>"
|
||||
}
|
||||
|
||||
func vimPlainString(_ string: String) -> String {
|
||||
return string.replacingOccurrences(of: "<", with: self.wrapNamedKeys("lt"))
|
||||
}
|
||||
|
||||
func updateFontMetaData(_ newFont: NSFont) {
|
||||
self.drawer.font = newFont
|
||||
|
||||
self.cellSize = self.drawer.cellSize
|
||||
self.descent = self.drawer.descent
|
||||
self.leading = self.drawer.leading
|
||||
|
||||
self.resizeNeoVimUi(to: self.bounds.size)
|
||||
}
|
||||
}
|
232
SwiftNeoVim/NeoVimView+Key.swift
Normal file
232
SwiftNeoVim/NeoVimView+Key.swift
Normal file
@ -0,0 +1,232 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension NeoVimView {
|
||||
|
||||
override public func keyDown(with event: NSEvent) {
|
||||
self.keyDownDone = false
|
||||
|
||||
let context = NSTextInputContext.current()!
|
||||
let cocoaHandledEvent = context.handleEvent(event)
|
||||
if self.keyDownDone && cocoaHandledEvent {
|
||||
return
|
||||
}
|
||||
|
||||
// self.logger.debug("\(#function): \(event)")
|
||||
|
||||
let modifierFlags = event.modifierFlags
|
||||
let capslock = modifierFlags.contains(.capsLock)
|
||||
let shift = modifierFlags.contains(.shift)
|
||||
let chars = event.characters!
|
||||
let charsIgnoringModifiers = shift || capslock
|
||||
? event.charactersIgnoringModifiers!.lowercased()
|
||||
: event.charactersIgnoringModifiers!
|
||||
|
||||
if KeyUtils.isSpecial(key: charsIgnoringModifiers) {
|
||||
if let vimModifiers = self.vimModifierFlags(modifierFlags) {
|
||||
self.agent.vimInput(
|
||||
self.wrapNamedKeys(vimModifiers + KeyUtils.namedKeyFrom(key: charsIgnoringModifiers))
|
||||
)
|
||||
} else {
|
||||
self.agent.vimInput(self.wrapNamedKeys(KeyUtils.namedKeyFrom(key: charsIgnoringModifiers)))
|
||||
}
|
||||
} else {
|
||||
if let vimModifiers = self.vimModifierFlags(modifierFlags) {
|
||||
self.agent.vimInput(self.wrapNamedKeys(vimModifiers + charsIgnoringModifiers))
|
||||
} else {
|
||||
self.agent.vimInput(self.vimPlainString(chars))
|
||||
}
|
||||
}
|
||||
|
||||
self.keyDownDone = true
|
||||
}
|
||||
|
||||
public func insertText(_ aString: Any, replacementRange: NSRange) {
|
||||
// self.logger.debug("\(#function): \(replacementRange): '\(aString)'")
|
||||
|
||||
switch aString {
|
||||
case let string as String:
|
||||
self.agent.vimInput(self.vimPlainString(string))
|
||||
case let attributedString as NSAttributedString:
|
||||
self.agent.vimInput(self.vimPlainString(attributedString.string))
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// unmarkText()
|
||||
self.lastMarkedText = self.markedText
|
||||
self.markedText = nil
|
||||
self.markedPosition = Position.null
|
||||
self.keyDownDone = true
|
||||
}
|
||||
|
||||
public override func doCommand(by aSelector: Selector) {
|
||||
// self.logger.debug("\(#function): \(aSelector)");
|
||||
|
||||
// FIXME: handle when ㅎ -> delete
|
||||
|
||||
if self.responds(to: aSelector) {
|
||||
Swift.print("\(#function): calling \(aSelector)")
|
||||
self.perform(aSelector, with: self)
|
||||
self.keyDownDone = true
|
||||
return
|
||||
}
|
||||
|
||||
// self.logger.debug("\(#function): "\(aSelector) not implemented, forwarding input to vim")
|
||||
self.keyDownDone = false
|
||||
}
|
||||
|
||||
public func setMarkedText(_ aString: Any, selectedRange: NSRange, replacementRange: NSRange) {
|
||||
if self.markedText == nil {
|
||||
self.markedPosition = self.grid.putPosition
|
||||
}
|
||||
|
||||
// eg 하 -> hanja popup, cf comment for self.lastMarkedText
|
||||
if replacementRange.length > 0 {
|
||||
self.agent.deleteCharacters(replacementRange.length)
|
||||
}
|
||||
|
||||
switch aString {
|
||||
case let string as String:
|
||||
self.markedText = string
|
||||
case let attributedString as NSAttributedString:
|
||||
self.markedText = attributedString.string
|
||||
default:
|
||||
self.markedText = String(describing: aString) // should not occur
|
||||
}
|
||||
|
||||
// self.logger.debug("\(#function): \(self.markedText), \(selectedRange), \(replacementRange)")
|
||||
|
||||
self.agent.vimInputMarkedText(self.markedText!)
|
||||
self.keyDownDone = true
|
||||
}
|
||||
|
||||
public func unmarkText() {
|
||||
// self.logger.debug("\(#function): ")
|
||||
self.markedText = nil
|
||||
self.markedPosition = Position.null
|
||||
self.keyDownDone = true
|
||||
|
||||
// TODO: necessary?
|
||||
self.markForRender(row: self.grid.putPosition.row, column: self.grid.putPosition.column)
|
||||
}
|
||||
|
||||
/// Return the current selection (or the position of the cursor with empty-length range).
|
||||
/// For example when you enter "Cmd-Ctrl-Return" you'll get the Emoji-popup at the rect
|
||||
/// by firstRectForCharacterRange(actualRange:) where the first range is the result of this method
|
||||
public func selectedRange() -> NSRange {
|
||||
// When the app starts and the Hangul input method is selected,
|
||||
// this method gets called very early...
|
||||
guard self.grid.hasData else {
|
||||
// self.logger.debug("\(#function): not found")
|
||||
return NSRange(location: NSNotFound, length: 0)
|
||||
}
|
||||
|
||||
let result = NSRange(location: self.grid.singleIndexFrom(self.grid.putPosition), length: 0)
|
||||
// self.logger.debug("\(#function): \(result)")
|
||||
return result
|
||||
}
|
||||
|
||||
public func markedRange() -> NSRange {
|
||||
// FIXME: do we have to handle positions at the column borders?
|
||||
if let markedText = self.markedText {
|
||||
let result = NSRange(location: self.grid.singleIndexFrom(self.markedPosition),
|
||||
length: markedText.characters.count)
|
||||
// self.logger.debug("\(#function): \(result)")
|
||||
return result
|
||||
}
|
||||
|
||||
self.logger.debug("\(#function): returning empty range")
|
||||
return NSRange(location: NSNotFound, length: 0)
|
||||
}
|
||||
|
||||
public func hasMarkedText() -> Bool {
|
||||
// self.logger.debug("\(#function)")
|
||||
return self.markedText != nil
|
||||
}
|
||||
|
||||
// FIXME: take into account the "return nil"-case
|
||||
// FIXME: just fix me, PLEASE...
|
||||
public func attributedSubstring(forProposedRange aRange: NSRange,
|
||||
actualRange: NSRangePointer?) -> NSAttributedString? {
|
||||
|
||||
// self.logger.debug("\(#function): \(aRange), \(actualRange[0])")
|
||||
if aRange.location == NSNotFound {
|
||||
// self.logger.debug("\(#function): range not found: returning nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let lastMarkedText = self.lastMarkedText else {
|
||||
// self.logger.debug("\(#function): no last marked text: returning nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
// we only support last marked text, thus fill dummy characters when Cocoa asks for more
|
||||
// characters than marked...
|
||||
let fillCount = aRange.length - lastMarkedText.characters.count
|
||||
guard fillCount >= 0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let fillChars = Array(0..<fillCount).reduce("") { (result, _) in return result + " " }
|
||||
|
||||
// self.logger.debug("\(#function): \(aRange), \(actualRange[0]): \(fillChars + lastMarkedText)")
|
||||
return NSAttributedString(string: fillChars + lastMarkedText)
|
||||
}
|
||||
|
||||
public func validAttributesForMarkedText() -> [String] {
|
||||
return []
|
||||
}
|
||||
|
||||
public func firstRect(forCharacterRange aRange: NSRange, actualRange: NSRangePointer?) -> NSRect {
|
||||
let position = self.grid.positionFromSingleIndex(aRange.location)
|
||||
|
||||
// self.logger.debug("\(#function): \(aRange),\(actualRange[0]) -> " +
|
||||
// "\(position.row):\(position.column)")
|
||||
|
||||
let resultInSelf = self.cellRectFor(row: position.row, column: position.column)
|
||||
let result = self.window?.convertToScreen(self.convert(resultInSelf, to: nil))
|
||||
|
||||
return result!
|
||||
}
|
||||
|
||||
public func characterIndex(for aPoint: NSPoint) -> Int {
|
||||
// self.logger.debug("\(#function): \(aPoint)")
|
||||
return 1
|
||||
}
|
||||
|
||||
func vimModifierFlags(_ modifierFlags: NSEventModifierFlags) -> String? {
|
||||
var result = ""
|
||||
|
||||
let control = modifierFlags.contains(.control)
|
||||
let option = modifierFlags.contains(.option)
|
||||
let command = modifierFlags.contains(.command)
|
||||
let shift = modifierFlags.contains(.shift)
|
||||
|
||||
if control {
|
||||
result += "C-"
|
||||
}
|
||||
|
||||
if option {
|
||||
result += "M-"
|
||||
}
|
||||
|
||||
if command {
|
||||
result += "D-"
|
||||
}
|
||||
|
||||
if shift {
|
||||
result += "S-"
|
||||
}
|
||||
|
||||
if result.characters.count > 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
161
SwiftNeoVim/NeoVimView+MenuItems.swift
Normal file
161
SwiftNeoVim/NeoVimView+MenuItems.swift
Normal file
@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
// MARK: - NSUserInterfaceValidationsProtocol
|
||||
extension NeoVimView {
|
||||
|
||||
public func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
||||
let canUndoOrRedo = self.mode == .insert || self.mode == .replace
|
||||
|| self.mode == .normal || self.mode == .visual
|
||||
let canCopyOrCut = self.mode == .normal || self.mode == .visual
|
||||
let canPaste = self.pasteboard.string(forType: NSPasteboardTypeString) != nil
|
||||
let canDelete = self.mode == .visual || self.mode == .normal
|
||||
let canSelectAll = self.mode == .insert || self.mode == .replace
|
||||
|| self.mode == .normal || self.mode == .visual
|
||||
|
||||
guard let action = item.action else {
|
||||
return true
|
||||
}
|
||||
|
||||
switch action {
|
||||
case #selector(undo(_:)), #selector(redo(_:)):
|
||||
return canUndoOrRedo
|
||||
case #selector(copy(_:)), #selector(cut(_:)):
|
||||
return canCopyOrCut
|
||||
case #selector(paste(_:)):
|
||||
return canPaste
|
||||
case #selector(delete(_:)):
|
||||
return canDelete
|
||||
case #selector(selectAll(_:)):
|
||||
return canSelectAll
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Edit Menu Items
|
||||
extension NeoVimView {
|
||||
|
||||
@IBAction func undo(_ sender: AnyObject?) {
|
||||
switch self.mode {
|
||||
case .insert, .replace:
|
||||
self.agent.vimInput("<Esc>ui")
|
||||
case .normal, .visual:
|
||||
self.agent.vimInput("u")
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func redo(_ sender: AnyObject?) {
|
||||
switch self.mode {
|
||||
case .insert, .replace:
|
||||
self.agent.vimInput("<Esc><C-r>i")
|
||||
case .normal, .visual:
|
||||
self.agent.vimInput("<C-r>")
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func cut(_ sender: AnyObject?) {
|
||||
switch self.mode {
|
||||
case .visual, .normal:
|
||||
self.agent.vimInput("\"+d")
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func copy(_ sender: AnyObject?) {
|
||||
switch self.mode {
|
||||
case .visual, .normal:
|
||||
self.agent.vimInput("\"+y")
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func paste(_ sender: AnyObject?) {
|
||||
guard let content = self.pasteboard.string(forType: NSPasteboardTypeString) else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.mode == .cmdline || self.mode == .cmdlineInsert || self.mode == .cmdlineReplace
|
||||
|| self.mode == .replace
|
||||
|| self.mode == .termFocus {
|
||||
self.agent.vimInput(self.vimPlainString(content))
|
||||
return
|
||||
}
|
||||
|
||||
guard let curPasteMode = self.agent.boolOption("paste") else {
|
||||
self.ipcBecameInvalid("Reason: 'set paste' failed")
|
||||
return
|
||||
}
|
||||
|
||||
let pasteModeSet: Bool
|
||||
|
||||
if curPasteMode == false {
|
||||
self.agent.setBoolOption("paste", to: true)
|
||||
pasteModeSet = true
|
||||
} else {
|
||||
pasteModeSet = false
|
||||
}
|
||||
|
||||
let resetPasteModeCmd = pasteModeSet ? ":set nopaste<CR>" : ""
|
||||
|
||||
switch self.mode {
|
||||
case .insert:
|
||||
self.agent.vimInput("<ESC>\"+p\(resetPasteModeCmd)a")
|
||||
case .normal, .visual:
|
||||
self.agent.vimInput("\"+p\(resetPasteModeCmd)")
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func delete(_ sender: AnyObject?) {
|
||||
switch self.mode {
|
||||
case .normal, .visual:
|
||||
self.agent.vimInput("x")
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction public override func selectAll(_ sender: Any?) {
|
||||
switch self.mode {
|
||||
case .insert, .visual:
|
||||
self.agent.vimInput("<Esc>ggVG")
|
||||
default:
|
||||
self.agent.vimInput("ggVG")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Font Menu Items
|
||||
extension NeoVimView {
|
||||
|
||||
@IBAction func resetFontSize(_ sender: Any?) {
|
||||
self.font = self._font
|
||||
}
|
||||
|
||||
@IBAction func makeFontBigger(_ sender: Any?) {
|
||||
let curFont = self.drawer.font
|
||||
let font = self.fontManager.convert(curFont,
|
||||
toSize: min(curFont.pointSize + 1, NeoVimView.maxFontSize))
|
||||
self.updateFontMetaData(font)
|
||||
}
|
||||
|
||||
@IBAction func makeFontSmaller(_ sender: Any?) {
|
||||
let curFont = self.drawer.font
|
||||
let font = self.fontManager.convert(curFont,
|
||||
toSize: max(curFont.pointSize - 1, NeoVimView.minFontSize))
|
||||
self.updateFontMetaData(font)
|
||||
}
|
||||
}
|
222
SwiftNeoVim/NeoVimView+Mouse.swift
Normal file
222
SwiftNeoVim/NeoVimView+Mouse.swift
Normal file
@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension NeoVimView {
|
||||
|
||||
override public func mouseDown(with event: NSEvent) {
|
||||
self.mouse(event: event, vimName: "LeftMouse")
|
||||
}
|
||||
|
||||
override public func mouseUp(with event: NSEvent) {
|
||||
self.mouse(event: event, vimName: "LeftRelease")
|
||||
}
|
||||
|
||||
override public func mouseDragged(with event: NSEvent) {
|
||||
self.mouse(event: event, vimName: "LeftDrag")
|
||||
}
|
||||
|
||||
override public func scrollWheel(with event: NSEvent) {
|
||||
let (deltaX, deltaY) = (event.scrollingDeltaX, event.scrollingDeltaY)
|
||||
if deltaX == 0 && deltaY == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
let isTrackpad = event.hasPreciseScrollingDeltas
|
||||
|
||||
let cellPosition = self.cellPositionFor(event: event)
|
||||
let (vimInputX, vimInputY) = self.vimScrollInputFor(deltaX: deltaX, deltaY: deltaY,
|
||||
modifierFlags: event.modifierFlags,
|
||||
cellPosition: cellPosition)
|
||||
|
||||
// We patched neovim such that it scrolls only 1 line for each scroll input.
|
||||
// The default is 3 and for mouse scrolling we restore the original behavior.
|
||||
if isTrackpad == false {
|
||||
(0..<3).forEach { _ in
|
||||
self.agent.vimInput(vimInputX)
|
||||
self.agent.vimInput(vimInputY)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let (absDeltaX, absDeltaY) = (abs(deltaX), abs(deltaY))
|
||||
|
||||
// The absolute delta values can get very very big when you use two finger scrolling
|
||||
// on the trackpad: Cap them using heuristic values...
|
||||
let numX = deltaX != 0 ?
|
||||
max(1, min(Int(absDeltaX / self.scrollLimiterX), self.maxScrollDeltaX)) : 0
|
||||
let numY = deltaY != 0 ?
|
||||
max(1, min(Int(absDeltaY / self.scrollLimiterY), self.maxScrollDeltaY)) : 0
|
||||
|
||||
for i in 0..<max(numX, numY) {
|
||||
if i < numX {
|
||||
self.throttleScrollX(absDelta: absDeltaX, vimInput: vimInputX)
|
||||
}
|
||||
|
||||
if i < numY {
|
||||
self.throttleScrollY(absDelta: absDeltaY, vimInput: vimInputY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func magnify(with event: NSEvent) {
|
||||
let factor = 1 + event.magnification
|
||||
let pinchTargetScale = self.pinchTargetScale * factor
|
||||
let resultingFontSize = round(pinchTargetScale * self.font.pointSize)
|
||||
if resultingFontSize >= NeoVimView.minFontSize && resultingFontSize <= NeoVimView.maxFontSize {
|
||||
self.pinchTargetScale = pinchTargetScale
|
||||
}
|
||||
|
||||
switch event.phase {
|
||||
case NSEventPhase.began:
|
||||
let pinchImageRep = self.bitmapImageRepForCachingDisplay(in: self.bounds)!
|
||||
self.cacheDisplay(in: self.bounds, to: pinchImageRep)
|
||||
self.pinchBitmap = pinchImageRep
|
||||
|
||||
self.isCurrentlyPinching = true
|
||||
self.needsDisplay = true
|
||||
|
||||
case NSEventPhase.ended, NSEventPhase.cancelled:
|
||||
self.isCurrentlyPinching = false
|
||||
self.updateFontMetaData(self.fontManager.convert(self.font, toSize: resultingFontSize))
|
||||
self.pinchTargetScale = 1
|
||||
|
||||
default:
|
||||
self.needsDisplay = true
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func cellPositionFor(event: NSEvent) -> Position {
|
||||
let location = self.convert(event.locationInWindow, from: nil)
|
||||
let row = Int((location.x - self.xOffset) / self.cellSize.width)
|
||||
let column = Int((self.bounds.size.height - location.y - self.yOffset) / self.cellSize.height)
|
||||
|
||||
let cellPosition = Position(row: min(max(0, row), self.grid.size.width - 1),
|
||||
column: min(max(0, column), self.grid.size.height - 1))
|
||||
return cellPosition
|
||||
}
|
||||
|
||||
fileprivate func mouse(event: NSEvent, vimName: String) {
|
||||
let cellPosition = self.cellPositionFor(event: event)
|
||||
guard self.shouldFireVimInputFor(event: event, newCellPosition: cellPosition) else {
|
||||
return
|
||||
}
|
||||
|
||||
let vimMouseLocation = self.wrapNamedKeys("\(cellPosition.row),\(cellPosition.column)")
|
||||
let vimClickCount = self.vimClickCountFrom(event: event)
|
||||
|
||||
let result: String
|
||||
if let vimModifiers = self.vimModifierFlags(event.modifierFlags) {
|
||||
result = self.wrapNamedKeys("\(vimModifiers)\(vimClickCount)\(vimName)") + vimMouseLocation
|
||||
} else {
|
||||
result = self.wrapNamedKeys("\(vimClickCount)\(vimName)") + vimMouseLocation
|
||||
}
|
||||
|
||||
// self.logger.debug("\(#function): \(result)")
|
||||
self.agent.vimInput(result)
|
||||
}
|
||||
|
||||
fileprivate func shouldFireVimInputFor(event: NSEvent, newCellPosition: Position) -> Bool {
|
||||
let type = event.type
|
||||
guard type == .leftMouseDragged || type == .rightMouseDragged
|
||||
|| type == .otherMouseDragged else {
|
||||
self.lastClickedCellPosition = newCellPosition
|
||||
return true
|
||||
}
|
||||
|
||||
if self.lastClickedCellPosition == newCellPosition {
|
||||
return false
|
||||
}
|
||||
|
||||
self.lastClickedCellPosition = newCellPosition
|
||||
return true
|
||||
}
|
||||
|
||||
fileprivate func vimClickCountFrom(event: NSEvent) -> String {
|
||||
let clickCount = event.clickCount
|
||||
|
||||
guard 2 <= clickCount && clickCount <= 4 else {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch event.type {
|
||||
case .leftMouseDown, .leftMouseUp, .rightMouseDown, .rightMouseUp:
|
||||
return "\(clickCount)-"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func vimScrollEventNamesFor(deltaX: CGFloat, deltaY: CGFloat) -> (String, String) {
|
||||
let typeY: String
|
||||
if deltaY > 0 {
|
||||
typeY = "ScrollWheelUp"
|
||||
} else {
|
||||
typeY = "ScrollWheelDown"
|
||||
}
|
||||
|
||||
let typeX: String
|
||||
if deltaX < 0 {
|
||||
typeX = "ScrollWheelRight"
|
||||
} else {
|
||||
typeX = "ScrollWheelLeft"
|
||||
}
|
||||
|
||||
return (typeX, typeY)
|
||||
}
|
||||
|
||||
fileprivate func vimScrollInputFor(deltaX: CGFloat, deltaY: CGFloat,
|
||||
modifierFlags: NSEventModifierFlags,
|
||||
cellPosition: Position) -> (String, String) {
|
||||
let vimMouseLocation = self.wrapNamedKeys("\(cellPosition.row),\(cellPosition.column)")
|
||||
|
||||
let (typeX, typeY) = self.vimScrollEventNamesFor(deltaX: deltaX, deltaY: deltaY)
|
||||
let resultX: String
|
||||
let resultY: String
|
||||
if let vimModifiers = self.vimModifierFlags(modifierFlags) {
|
||||
resultX = self.wrapNamedKeys("\(vimModifiers)\(typeX)") + vimMouseLocation
|
||||
resultY = self.wrapNamedKeys("\(vimModifiers)\(typeY)") + vimMouseLocation
|
||||
} else {
|
||||
resultX = self.wrapNamedKeys("\(typeX)") + vimMouseLocation
|
||||
resultY = self.wrapNamedKeys("\(typeY)") + vimMouseLocation
|
||||
}
|
||||
|
||||
return (resultX, resultY)
|
||||
}
|
||||
|
||||
fileprivate func throttleScrollX(absDelta absDeltaX: CGFloat, vimInput: String) {
|
||||
if absDeltaX == 0 {
|
||||
self.scrollGuardCounterX = self.scrollGuardYield - 1
|
||||
} else if absDeltaX <= 2 {
|
||||
// Poor man's throttle for scroll value = 1 or 2
|
||||
if self.scrollGuardCounterX % self.scrollGuardYield == 0 {
|
||||
self.agent.vimInput(vimInput)
|
||||
self.scrollGuardCounterX = 1
|
||||
} else {
|
||||
self.scrollGuardCounterX += 1
|
||||
}
|
||||
} else {
|
||||
self.agent.vimInput(vimInput)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func throttleScrollY(absDelta absDeltaY: CGFloat, vimInput: String) {
|
||||
if absDeltaY == 0 {
|
||||
self.scrollGuardCounterY = self.scrollGuardYield - 1
|
||||
} else if absDeltaY <= 2 {
|
||||
// Poor man's throttle for scroll value = 1 or 2
|
||||
if self.scrollGuardCounterY % self.scrollGuardYield == 0 {
|
||||
self.agent.vimInput(vimInput)
|
||||
self.scrollGuardCounterY = 1
|
||||
} else {
|
||||
self.scrollGuardCounterY += 1
|
||||
}
|
||||
} else {
|
||||
self.agent.vimInput(vimInput)
|
||||
}
|
||||
}
|
||||
}
|
53
SwiftNeoVim/NeoVimView+Resize.swift
Normal file
53
SwiftNeoVim/NeoVimView+Resize.swift
Normal file
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension NeoVimView {
|
||||
|
||||
override public func setFrameSize(_ newSize: NSSize) {
|
||||
super.setFrameSize(newSize)
|
||||
|
||||
// initial resizing is done when grid has data
|
||||
guard self.grid.hasData else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.inLiveResize || self.currentlyResizing {
|
||||
// TODO: Turn off live resizing for now.
|
||||
// self.resizeNeoVimUi(to: newSize)
|
||||
return
|
||||
}
|
||||
|
||||
// There can be cases where the frame is resized not by live resizing,
|
||||
// eg when the window is resized by window management tools.
|
||||
// Thus, we make sure that the resize call is made when this happens.
|
||||
self.resizeNeoVimUi(to: newSize)
|
||||
}
|
||||
|
||||
override public func viewDidEndLiveResize() {
|
||||
super.viewDidEndLiveResize()
|
||||
self.resizeNeoVimUi(to: self.bounds.size)
|
||||
}
|
||||
|
||||
func discreteSize(size: CGSize) -> Size {
|
||||
return Size(width: Int(floor(size.width / self.cellSize.width)),
|
||||
height: Int(floor(size.height / self.cellSize.height)))
|
||||
}
|
||||
|
||||
func resizeNeoVimUi(to size: CGSize) {
|
||||
self.currentEmoji = self.randomEmoji()
|
||||
|
||||
let discreteSize = self.discreteSize(size: size)
|
||||
if discreteSize == self.grid.size {
|
||||
return
|
||||
}
|
||||
|
||||
self.xOffset = floor((size.width - self.cellSize.width * CGFloat(discreteSize.width)) / 2)
|
||||
self.yOffset = floor((size.height - self.cellSize.height * CGFloat(discreteSize.height)) / 2)
|
||||
|
||||
self.agent.resize(toWidth: Int32(discreteSize.width), height: Int32(discreteSize.height))
|
||||
}
|
||||
}
|
406
SwiftNeoVim/NeoVimView+UiBridge.swift
Normal file
406
SwiftNeoVim/NeoVimView+UiBridge.swift
Normal file
@ -0,0 +1,406 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension NeoVimView {
|
||||
|
||||
public func resize(toWidth width: Int, height: Int) {
|
||||
gui.async {
|
||||
self.logger.debug("\(width) x \(height)")
|
||||
|
||||
self.grid.resize(Size(width: width, height: height))
|
||||
self.markForRenderWholeView()
|
||||
}
|
||||
}
|
||||
|
||||
public func clear() {
|
||||
gui.async {
|
||||
self.logger.mark()
|
||||
|
||||
self.grid.clear()
|
||||
self.markForRenderWholeView()
|
||||
}
|
||||
}
|
||||
|
||||
public func eolClear() {
|
||||
gui.async {
|
||||
self.logger.mark()
|
||||
|
||||
self.grid.eolClear()
|
||||
|
||||
let putPosition = self.grid.putPosition
|
||||
let region = Region(top: putPosition.row,
|
||||
bottom: putPosition.row,
|
||||
left: putPosition.column,
|
||||
right: self.grid.region.right)
|
||||
self.markForRender(region: region)
|
||||
}
|
||||
}
|
||||
|
||||
public func gotoPosition(_ position: Position,
|
||||
screenCursor: Position,
|
||||
currentPosition: Position) {
|
||||
|
||||
gui.async {
|
||||
self.logger.debug("pos: \(position), screen: \(screenCursor), " +
|
||||
"current-pos: \(currentPosition)")
|
||||
|
||||
self.currentPosition = currentPosition
|
||||
let curScreenCursor = self.grid.screenCursor
|
||||
|
||||
// Because neovim fills blank space with "Space" and when we enter "Space"
|
||||
// we don't get the puts, thus we have to redraw the put position.
|
||||
if self.usesLigatures {
|
||||
self.markForRender(region: self.grid.regionOfWord(at: self.grid.putPosition))
|
||||
self.markForRender(region: self.grid.regionOfWord(at: curScreenCursor))
|
||||
self.markForRender(region: self.grid.regionOfWord(at: position))
|
||||
self.markForRender(region: self.grid.regionOfWord(at: screenCursor))
|
||||
} else {
|
||||
self.markForRender(cellPosition: self.grid.putPosition)
|
||||
// Redraw where the cursor has been till now, ie remove the current cursor.
|
||||
self.markForRender(cellPosition: curScreenCursor)
|
||||
if self.grid.isPreviousCellEmpty(curScreenCursor) {
|
||||
self.markForRender(cellPosition: self.grid.previousCellPosition(curScreenCursor))
|
||||
}
|
||||
if self.grid.isNextCellEmpty(curScreenCursor) {
|
||||
self.markForRender(cellPosition: self.grid.nextCellPosition(curScreenCursor))
|
||||
}
|
||||
self.markForRender(cellPosition: position)
|
||||
self.markForRender(cellPosition: screenCursor)
|
||||
}
|
||||
|
||||
self.grid.goto(position)
|
||||
self.grid.moveCursor(screenCursor)
|
||||
}
|
||||
gui.async {
|
||||
self.delegate?.cursor(to: currentPosition)
|
||||
}
|
||||
}
|
||||
|
||||
public func modeChange(_ mode: CursorModeShape) {
|
||||
gui.async {
|
||||
self.logger.debug(cursorModeShapeName(mode))
|
||||
self.mode = mode
|
||||
}
|
||||
}
|
||||
|
||||
public func setScrollRegionToTop(_ top: Int, bottom: Int, left: Int, right: Int) {
|
||||
gui.async {
|
||||
self.logger.debug("\(top):\(bottom):\(left):\(right)")
|
||||
|
||||
let region = Region(top: top, bottom: bottom, left: left, right: right)
|
||||
self.grid.setScrollRegion(region)
|
||||
}
|
||||
}
|
||||
|
||||
public func scroll(_ count: Int) {
|
||||
gui.async {
|
||||
self.logger.debug(count)
|
||||
|
||||
self.grid.scroll(count)
|
||||
self.markForRender(region: self.grid.region)
|
||||
// Do not send msgs to agent -> neovim in the delegate method. It causes spinning
|
||||
// when you're opening a file with existing swap file.
|
||||
self.delegate?.scroll()
|
||||
}
|
||||
}
|
||||
|
||||
public func highlightSet(_ attrs: CellAttributes) {
|
||||
gui.async {
|
||||
self.logger.debug(attrs)
|
||||
|
||||
self.grid.attrs = attrs
|
||||
}
|
||||
}
|
||||
|
||||
public func put(_ string: String, screenCursor: Position) {
|
||||
gui.async {
|
||||
self.logger.debug("'\(string)' <- screen: \(screenCursor)")
|
||||
|
||||
let curPos = self.grid.putPosition
|
||||
// self.logger.debug("\(#function): \(curPos) -> \(string)")
|
||||
self.grid.put(string)
|
||||
|
||||
if self.usesLigatures {
|
||||
if string == " " {
|
||||
self.markForRender(cellPosition: curPos)
|
||||
} else {
|
||||
self.markForRender(region: self.grid.regionOfWord(at: curPos))
|
||||
}
|
||||
} else {
|
||||
self.markForRender(cellPosition: curPos)
|
||||
}
|
||||
|
||||
self.updateCursorWhenPutting(currentPosition: curPos, screenCursor: screenCursor)
|
||||
}
|
||||
}
|
||||
|
||||
public func putMarkedText(_ markedText: String, screenCursor: Position) {
|
||||
gui.async {
|
||||
self.logger.debug("'\(markedText)' <- screen: \(screenCursor)")
|
||||
|
||||
let curPos = self.grid.putPosition
|
||||
self.grid.putMarkedText(markedText)
|
||||
|
||||
self.markForRender(position: curPos)
|
||||
// When the cursor is in the command line, then we need this...
|
||||
self.markForRender(cellPosition: self.grid.nextCellPosition(curPos))
|
||||
if markedText.characters.count == 0 {
|
||||
self.markForRender(position: self.grid.previousCellPosition(curPos))
|
||||
}
|
||||
|
||||
self.updateCursorWhenPutting(currentPosition: curPos, screenCursor: screenCursor)
|
||||
}
|
||||
}
|
||||
|
||||
public func unmarkRow(_ row: Int, column: Int) {
|
||||
gui.async {
|
||||
self.logger.debug("\(row):\(column)")
|
||||
|
||||
let position = Position(row: row, column: column)
|
||||
|
||||
self.grid.unmarkCell(position)
|
||||
self.markForRender(position: position)
|
||||
|
||||
self.markForRender(screenCursor: self.grid.screenCursor)
|
||||
}
|
||||
}
|
||||
|
||||
public func flush() {
|
||||
gui.async {
|
||||
self.logger.debug("-----------------------------")
|
||||
}
|
||||
}
|
||||
|
||||
public func updateForeground(_ fg: Int) {
|
||||
gui.async {
|
||||
self.logger.debug(ColorUtils.colorIgnoringAlpha(fg))
|
||||
|
||||
self.grid.foreground = fg
|
||||
}
|
||||
}
|
||||
|
||||
public func updateBackground(_ bg: Int) {
|
||||
gui.async {
|
||||
self.logger.debug(ColorUtils.colorIgnoringAlpha(bg))
|
||||
|
||||
self.grid.background = bg
|
||||
self.layer?.backgroundColor = ColorUtils.colorIgnoringAlpha(self.grid.background).cgColor
|
||||
}
|
||||
}
|
||||
|
||||
public func updateSpecial(_ sp: Int) {
|
||||
gui.async {
|
||||
self.logger.debug(ColorUtils.colorIgnoringAlpha(sp))
|
||||
|
||||
self.grid.special = sp
|
||||
}
|
||||
}
|
||||
|
||||
public func setTitle(_ title: String) {
|
||||
gui.async {
|
||||
self.logger.debug(title)
|
||||
|
||||
self.delegate?.set(title: title)
|
||||
}
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
gui.async {
|
||||
self.logger.mark()
|
||||
|
||||
self.delegate?.neoVimStopped()
|
||||
}
|
||||
self.agent.quit()
|
||||
}
|
||||
|
||||
public func autoCommandEvent(_ event: NeoVimAutoCommandEvent, bufferHandle: Int) {
|
||||
gui.async {
|
||||
self.logger.debug("\(neoVimAutoCommandEventName(event)) -> \(bufferHandle)")
|
||||
|
||||
if event == .BUFWINENTER || event == .BUFWINLEAVE {
|
||||
self.bufferListChanged()
|
||||
}
|
||||
|
||||
if event == .TABENTER {
|
||||
self.tabChanged()
|
||||
}
|
||||
|
||||
if event == .DIRCHANGED {
|
||||
self.cwdChanged()
|
||||
}
|
||||
|
||||
if event == .BUFREADPOST || event == .BUFWRITEPOST {
|
||||
self.currentBufferChanged(bufferHandle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func ipcBecameInvalid(_ reason: String) {
|
||||
gui.async {
|
||||
self.logger.debug(reason)
|
||||
|
||||
if self.agent.neoVimIsQuitting {
|
||||
return
|
||||
}
|
||||
|
||||
self.delegate?.ipcBecameInvalid(reason: reason)
|
||||
|
||||
self.logger.fault("force-quitting")
|
||||
self.agent.quit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Simple
|
||||
extension NeoVimView {
|
||||
|
||||
public func bell() {
|
||||
gui.async {
|
||||
self.logger.mark()
|
||||
|
||||
NSBeep()
|
||||
}
|
||||
}
|
||||
|
||||
public func setDirtyStatus(_ dirty: Bool) {
|
||||
gui.async {
|
||||
self.logger.debug(dirty)
|
||||
|
||||
self.delegate?.set(dirtyStatus: dirty)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateMenu() {
|
||||
gui.async {
|
||||
self.logger.mark()
|
||||
}
|
||||
}
|
||||
|
||||
public func busyStart() {
|
||||
gui.async {
|
||||
self.logger.mark()
|
||||
}
|
||||
}
|
||||
|
||||
public func busyStop() {
|
||||
gui.async {
|
||||
self.logger.mark()
|
||||
}
|
||||
}
|
||||
|
||||
public func mouseOn() {
|
||||
gui.async {
|
||||
self.logger.mark()
|
||||
}
|
||||
}
|
||||
|
||||
public func mouseOff() {
|
||||
gui.async {
|
||||
self.logger.mark()
|
||||
}
|
||||
}
|
||||
|
||||
public func visualBell() {
|
||||
gui.async {
|
||||
self.logger.mark()
|
||||
}
|
||||
}
|
||||
|
||||
public func suspend() {
|
||||
gui.async {
|
||||
self.logger.mark()
|
||||
}
|
||||
}
|
||||
|
||||
public func setIcon(_ icon: String) {
|
||||
gui.async {
|
||||
self.logger.debug(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NeoVimView {
|
||||
|
||||
func markForRender(cellPosition position: Position) {
|
||||
self.markForRender(position: position)
|
||||
|
||||
if self.grid.isCellEmpty(position) {
|
||||
self.markForRender(position: self.grid.previousCellPosition(position))
|
||||
}
|
||||
|
||||
if self.grid.isNextCellEmpty(position) {
|
||||
self.markForRender(position: self.grid.nextCellPosition(position))
|
||||
}
|
||||
}
|
||||
|
||||
func markForRender(position: Position) {
|
||||
self.markForRender(row: position.row, column: position.column)
|
||||
}
|
||||
|
||||
func markForRender(screenCursor position: Position) {
|
||||
self.markForRender(position: position)
|
||||
if self.grid.isNextCellEmpty(position) {
|
||||
self.markForRender(position: self.grid.nextCellPosition(position))
|
||||
}
|
||||
}
|
||||
|
||||
func markForRenderWholeView() {
|
||||
self.needsDisplay = true
|
||||
}
|
||||
|
||||
func markForRender(region: Region) {
|
||||
self.setNeedsDisplay(self.regionRectFor(region: region))
|
||||
}
|
||||
|
||||
func markForRender(row: Int, column: Int) {
|
||||
self.setNeedsDisplay(self.cellRectFor(row: row, column: column))
|
||||
}
|
||||
}
|
||||
|
||||
extension NeoVimView {
|
||||
|
||||
fileprivate func currentBufferChanged(_ handle: Int) {
|
||||
guard let currentBuffer = self.currentBuffer() else {
|
||||
return
|
||||
}
|
||||
|
||||
guard currentBuffer.handle == handle else {
|
||||
return
|
||||
}
|
||||
|
||||
self.delegate?.currentBufferChanged(currentBuffer)
|
||||
}
|
||||
|
||||
fileprivate func tabChanged() {
|
||||
self.delegate?.tabChanged()
|
||||
}
|
||||
|
||||
fileprivate func cwdChanged() {
|
||||
self.delegate?.cwdChanged()
|
||||
}
|
||||
|
||||
fileprivate func bufferListChanged() {
|
||||
self.delegate?.bufferListChanged()
|
||||
}
|
||||
|
||||
fileprivate func updateCursorWhenPutting(currentPosition curPos: Position,
|
||||
screenCursor: Position) {
|
||||
|
||||
if self.mode == .cmdline {
|
||||
// When the cursor is in the command line, then we need this...
|
||||
self.markForRender(cellPosition: self.grid.previousCellPosition(curPos))
|
||||
self.markForRender(cellPosition: self.grid.nextCellPosition(curPos))
|
||||
self.markForRender(screenCursor: self.grid.screenCursor)
|
||||
}
|
||||
|
||||
self.markForRender(screenCursor: screenCursor)
|
||||
self.markForRender(cellPosition: self.grid.screenCursor)
|
||||
self.grid.moveCursor(screenCursor)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let gui = DispatchQueue.main
|
File diff suppressed because it is too large
Load Diff
@ -48,6 +48,7 @@
|
||||
1929B71381946860626E5224 /* FileBrowserReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BDC8F5D48578A90236E9 /* FileBrowserReducer.swift */; };
|
||||
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 */; };
|
||||
1929B8E90A1378E494D481E7 /* PrefUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B7BB3E4B3DC96284B686 /* PrefUtilsTest.swift */; };
|
||||
1929B8EE07CA6D47855E7332 /* ImageAndTextTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B015804B46789F719C01 /* ImageAndTextTableCell.swift */; };
|
||||
1929B8FB248D71BF88A35761 /* PreviewTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B6C6C7792B05164B0216 /* PreviewTool.swift */; };
|
||||
@ -59,14 +60,20 @@
|
||||
1929BAA19A6BEE73963BD4A8 /* NeoVimWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B868F11F398A159EF554 /* NeoVimWindow.swift */; };
|
||||
1929BAAD7336FDFF1F78E749 /* ScorerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BF69B01107F358CF7EAD /* ScorerTest.swift */; };
|
||||
1929BAE4900D72A7877741B1 /* PrefWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BE168F31344B69E61B62 /* PrefWindow.swift */; };
|
||||
1929BAE6B93067FD52DBE892 /* NeoVimView+Mouse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B4A088958A7B4FF152B7 /* NeoVimView+Mouse.swift */; };
|
||||
1929BAFF1E011321D3186EE6 /* UiRoot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD4149D5A25C82064DD8 /* UiRoot.swift */; };
|
||||
1929BB0A840D93D1A13C25FD /* NeoVimView+MenuItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B4F65149D3C3E326DA65 /* NeoVimView+MenuItems.swift */; };
|
||||
1929BB38525ECCEE8E39288A /* NeoVimView+Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BDF3167E15E7F3349798 /* NeoVimView+Key.swift */; };
|
||||
1929BB4A9B2FA42A64CCCC76 /* MainWindowReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD83A13BF133741766CC /* MainWindowReducer.swift */; };
|
||||
1929BC9EF45463113EBF3BCB /* NeoVimView+UiBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B2AD3F9375F57F21AD58 /* NeoVimView+UiBridge.swift */; };
|
||||
1929BCC7908DD899999B70BE /* AppearancePrefReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BED01F5D94BFCA4CF80F /* AppearancePrefReducer.swift */; };
|
||||
1929BCC9D3604933DFF07E2E /* FileBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BA5C7099CDEB04B76BA4 /* FileBrowser.swift */; };
|
||||
1929BCF7F7B9CC5499A3F506 /* AdvancedPrefReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B7039C5689CE45F53888 /* AdvancedPrefReducer.swift */; };
|
||||
1929BD294A33873BAE2804C0 /* NeoVimView+Resize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD05CA498FC3C229455E /* NeoVimView+Resize.swift */; };
|
||||
1929BD2F41D93ADFF43C1C98 /* NetUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1929B02440BC99C42F9EBD45 /* NetUtils.m */; };
|
||||
1929BD3878A3A47B8D685CD2 /* AppDelegateReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B7A68B7109CEFAF105E8 /* AppDelegateReducer.swift */; };
|
||||
1929BD3F9E609BFADB27584B /* Scorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B9D510177918080BE39B /* Scorer.swift */; };
|
||||
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 */; };
|
||||
1929BEAE0592096BC1191B67 /* PrefPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B07A4A9209C88380E015 /* PrefPane.swift */; };
|
||||
@ -147,6 +154,9 @@
|
||||
4B8AC0441DBCB3A2007CCC9B /* NeoVimObjectsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8AC0431DBCB3A1007CCC9B /* NeoVimObjectsExtensions.swift */; };
|
||||
4B91FFF41DEB772200447068 /* CocoaFontAwesome.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B337FBA1DEB76F20020ADD2 /* CocoaFontAwesome.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
4B91FFF61DEB772B00447068 /* CocoaFontAwesome.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B337FBA1DEB76F20020ADD2 /* CocoaFontAwesome.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
4B96384C1ED9793B001C556F /* NeoVimAutoCommandEvent.generated.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B96384B1ED9793B001C556F /* NeoVimAutoCommandEvent.generated.m */; };
|
||||
4B96384E1ED9797F001C556F /* NeoVimUiBridgeProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B96384D1ED9797F001C556F /* NeoVimUiBridgeProtocol.m */; };
|
||||
4B9638501ED979BD001C556F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B96384F1ED979BD001C556F /* Logger.swift */; };
|
||||
4B96FB3B1EBBC56F00E4E164 /* FileItemIgnorePattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B69499B2569793350CEC /* FileItemIgnorePattern.swift */; };
|
||||
4B96FB3C1EBBC56F00E4E164 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BA8AC40B901B20F20B71 /* FileUtils.swift */; };
|
||||
4B96FB3D1EBBC56F00E4E164 /* Matcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BEEB33113B0E33C3830F /* Matcher.swift */; };
|
||||
@ -362,13 +372,17 @@
|
||||
1929B12CE56A9B36980288A4 /* OpenQuicklyReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenQuicklyReducer.swift; sourceTree = "<group>"; };
|
||||
1929B1558455B3A74D93EF2A /* OpenQuicklyFileViewRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenQuicklyFileViewRow.swift; sourceTree = "<group>"; };
|
||||
1929B15B7EDC9B0F40E5E95C /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = "<group>"; };
|
||||
1929B19207FBC2EBDF1B88F3 /* NeoVimView+Draw.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NeoVimView+Draw.swift"; sourceTree = "<group>"; };
|
||||
1929B1A51F076E088EF4CCA4 /* server_globals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = server_globals.h; sourceTree = "<group>"; };
|
||||
1929B1DC584C89C477E83FA2 /* HttpServerService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServerService.swift; sourceTree = "<group>"; };
|
||||
1929B2AD3F9375F57F21AD58 /* NeoVimView+UiBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NeoVimView+UiBridge.swift"; sourceTree = "<group>"; };
|
||||
1929B34FC23D805A8B29E8F7 /* Context.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = "<group>"; };
|
||||
1929B364460D86F17E80943C /* PrefService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefService.swift; sourceTree = "<group>"; };
|
||||
1929B365A6434354B568B04F /* FileMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileMonitor.swift; sourceTree = "<group>"; };
|
||||
1929B457B9D0FA4D21F3751E /* UiRootReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UiRootReducer.swift; sourceTree = "<group>"; };
|
||||
1929B49E6924847AD085C8C9 /* PrefWindowReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefWindowReducer.swift; sourceTree = "<group>"; };
|
||||
1929B4A088958A7B4FF152B7 /* NeoVimView+Mouse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NeoVimView+Mouse.swift"; sourceTree = "<group>"; };
|
||||
1929B4F65149D3C3E326DA65 /* NeoVimView+MenuItems.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NeoVimView+MenuItems.swift"; sourceTree = "<group>"; };
|
||||
1929B5046239709E33516F5C /* Pref128ToCurrentConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pref128ToCurrentConverter.swift; sourceTree = "<group>"; };
|
||||
1929B56C8ED31834BA9D8543 /* FileItemUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemUtils.swift; sourceTree = "<group>"; };
|
||||
1929B5C3F2F1CA4113DABFFD /* CocoaCategories.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CocoaCategories.m; sourceTree = "<group>"; };
|
||||
@ -409,10 +423,13 @@
|
||||
1929BBC84557C8351EC6183E /* FileItemIgnorePatternTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemIgnorePatternTest.swift; sourceTree = "<group>"; };
|
||||
1929BBE0A534F2F6009D31BE /* AdvencedPref.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvencedPref.swift; sourceTree = "<group>"; };
|
||||
1929BCE3E156C06EDF1F2806 /* FileOutlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileOutlineView.swift; sourceTree = "<group>"; };
|
||||
1929BD05CA498FC3C229455E /* NeoVimView+Resize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NeoVimView+Resize.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>"; };
|
||||
1929BDC8F5D48578A90236E9 /* FileBrowserReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileBrowserReducer.swift; sourceTree = "<group>"; };
|
||||
1929BDF3167E15E7F3349798 /* NeoVimView+Key.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NeoVimView+Key.swift"; sourceTree = "<group>"; };
|
||||
1929BDF9EBAF1D9D44399045 /* ScoredFileItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScoredFileItem.swift; sourceTree = "<group>"; };
|
||||
1929BE168F31344B69E61B62 /* PrefWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefWindow.swift; sourceTree = "<group>"; };
|
||||
1929BE37AA2843779CAFA76F /* PreviewReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewReducer.swift; sourceTree = "<group>"; };
|
||||
@ -480,6 +497,9 @@
|
||||
4B854A1A1D31447C00E08DE1 /* NeoVimServer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = NeoVimServer; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4B854A1C1D31447C00E08DE1 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
4B8AC0431DBCB3A1007CCC9B /* NeoVimObjectsExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeoVimObjectsExtensions.swift; sourceTree = "<group>"; };
|
||||
4B96384B1ED9793B001C556F /* NeoVimAutoCommandEvent.generated.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NeoVimAutoCommandEvent.generated.m; sourceTree = "<group>"; };
|
||||
4B96384D1ED9797F001C556F /* NeoVimUiBridgeProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NeoVimUiBridgeProtocol.m; sourceTree = "<group>"; };
|
||||
4B96384F1ED979BD001C556F /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
4B97E2CD1D33F53D00FC0660 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainWindow.xib; sourceTree = "<group>"; };
|
||||
4BB1BEA81D48773200463C29 /* RxSwiftCommons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxSwiftCommons.swift; sourceTree = "<group>"; };
|
||||
4BB409E61DD68CCC005F39A2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/FileBrowserMenu.xib; sourceTree = "<group>"; };
|
||||
@ -775,6 +795,7 @@
|
||||
4BDF641B1D0887C100D47E1D /* TextDrawer.m */,
|
||||
4BDF64221D08CAB000D47E1D /* MMCoreTextView.h */,
|
||||
4BDF64231D08CAB000D47E1D /* MMCoreTextView.m */,
|
||||
4B96384F1ED979BD001C556F /* Logger.swift */,
|
||||
4B4192171D0C52D700A0BEB2 /* Grid.swift */,
|
||||
4BCADE071D11ED12004DAD0F /* CocoaExtensions.swift */,
|
||||
4B1BB3521D16C5E500CA4FEF /* InputTestView.swift */,
|
||||
@ -1012,7 +1033,16 @@
|
||||
4B570DC01D303CAF006EDC21 /* NeoVimAgent.h */,
|
||||
4B570DC11D303CAF006EDC21 /* NeoVimAgent.m */,
|
||||
4B183E1A1E08748B0079E8A8 /* NeoVimAutoCommandEvent.generated.h */,
|
||||
4B96384B1ED9793B001C556F /* NeoVimAutoCommandEvent.generated.m */,
|
||||
4B2A2C061D0352CB0074CE9A /* NeoVimUiBridgeProtocol.h */,
|
||||
4B96384D1ED9797F001C556F /* NeoVimUiBridgeProtocol.m */,
|
||||
1929BD8F08E21E3ECE9F84AB /* NeoVimView+Api.swift */,
|
||||
1929B2AD3F9375F57F21AD58 /* NeoVimView+UiBridge.swift */,
|
||||
1929B4A088958A7B4FF152B7 /* NeoVimView+Mouse.swift */,
|
||||
1929B19207FBC2EBDF1B88F3 /* NeoVimView+Draw.swift */,
|
||||
1929BD05CA498FC3C229455E /* NeoVimView+Resize.swift */,
|
||||
1929BDF3167E15E7F3349798 /* NeoVimView+Key.swift */,
|
||||
1929B4F65149D3C3E326DA65 /* NeoVimView+MenuItems.swift */,
|
||||
);
|
||||
name = NeoVimView;
|
||||
sourceTree = "<group>";
|
||||
@ -1378,7 +1408,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "pushd neovim\nmake CFLAGS='-mmacosx-version-min=10.10' MACOSX_DEPLOYMENT_TARGET=10.10 CMAKE_EXTRA_FLAGS=\"-DGETTEXT_SOURCE=CUSTOM\" libnvim\npopd";
|
||||
shellScript = "pushd neovim\n../bin/build_libnvim.sh\npopd";
|
||||
};
|
||||
4BDFAC751E59BAC30056122C /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@ -1391,7 +1421,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if [ \"${CONFIGURATION}\" = \"Debug\" ]; then\nif [ -f \"SwiftNeoVim/NeoVimAutoCommandEvent.generated.h\" ]; then\nexit 0\nfi\nfi\n\nsed 's/^typedef enum auto_event/typedef NS_ENUM(NSUInteger, NeoVimAutoCommandEvent)/' <./neovim/build/include/auevents_enum.generated.h | sed 's/ event_T//' | sed 's/EVENT_/NeoVimAutoCommandEvent/' | sed 's/NUM_EVENTS/NumberOfAutoCommandEvents/' | sed -e '1s/^/@import Foundation;\\'$'\\n/' > SwiftNeoVim/NeoVimAutoCommandEvent.generated.h\n";
|
||||
shellScript = ./bin/generate_source.py;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
@ -1400,6 +1430,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4B96384C1ED9793B001C556F /* NeoVimAutoCommandEvent.generated.m in Sources */,
|
||||
4BDD05891DBBC50000D1B405 /* NeoVimTab.m in Sources */,
|
||||
4BEE79171D16D3800012EDAA /* CellAttributes.swift in Sources */,
|
||||
4BDD05861DBBC50000D1B405 /* NeoVimBuffer.m in Sources */,
|
||||
@ -1407,13 +1438,22 @@
|
||||
4BCADE081D11ED12004DAD0F /* CocoaExtensions.swift in Sources */,
|
||||
4B401B1A1D046E0600D99EDC /* NeoVimViewDelegate.swift in Sources */,
|
||||
1929B728262BAA14FC93F6AC /* NeoVimView.swift in Sources */,
|
||||
4B9638501ED979BD001C556F /* Logger.swift in Sources */,
|
||||
4B570DC31D303CAF006EDC21 /* NeoVimAgent.m in Sources */,
|
||||
4BDF641D1D0887C100D47E1D /* TextDrawer.m in Sources */,
|
||||
4BDF64251D08CAB000D47E1D /* MMCoreTextView.m in Sources */,
|
||||
4BDD058C1DBBC50000D1B405 /* NeoVimWindow.m in Sources */,
|
||||
1929BEB90DCDAF7A2B68C886 /* ColorUtils.swift in Sources */,
|
||||
4B8AC0441DBCB3A2007CCC9B /* NeoVimObjectsExtensions.swift in Sources */,
|
||||
4B96384E1ED9797F001C556F /* NeoVimUiBridgeProtocol.m in Sources */,
|
||||
4B4192181D0C52D700A0BEB2 /* Grid.swift in Sources */,
|
||||
1929BDD4BFA4175D0A1B0BC3 /* NeoVimView+Api.swift in Sources */,
|
||||
1929BC9EF45463113EBF3BCB /* NeoVimView+UiBridge.swift in Sources */,
|
||||
1929BAE6B93067FD52DBE892 /* NeoVimView+Mouse.swift in Sources */,
|
||||
1929B7993C8DB7F59447DF5F /* NeoVimView+Draw.swift in Sources */,
|
||||
1929BD294A33873BAE2804C0 /* NeoVimView+Resize.swift in Sources */,
|
||||
1929BB38525ECCEE8E39288A /* NeoVimView+Key.swift in Sources */,
|
||||
1929BB0A840D93D1A13C25FD /* NeoVimView+MenuItems.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -174,18 +174,6 @@ class FileOutlineView: NSOutlineView,
|
||||
self.insertItems(at: IndexSet(indicesToInsert), inParent: parent)
|
||||
}
|
||||
|
||||
fileprivate func handleChildren(for fileBrowserItem: FileBrowserItem, new newChildren: [FileBrowserItem]) {
|
||||
let curChildren = fileBrowserItem.children
|
||||
|
||||
let curPreparedChildren = self.prepare(curChildren)
|
||||
let newPreparedChildren = self.prepare(newChildren)
|
||||
|
||||
let keptChildren = curPreparedChildren.filter { newPreparedChildren.contains($0) }
|
||||
let childrenToRecurse = keptChildren.filter { self.isItemExpanded($0) }
|
||||
|
||||
childrenToRecurse.forEach(self.update)
|
||||
}
|
||||
|
||||
fileprivate func sortedChildren(of url: URL) -> [FileBrowserItem] {
|
||||
return FileUtils.directDescendants(of: url).map(FileBrowserItem.init).sorted()
|
||||
}
|
||||
@ -198,7 +186,7 @@ class FileOutlineView: NSOutlineView,
|
||||
|
||||
self.handleRemovals(for: fileBrowserItem, new: newChildren)
|
||||
self.handleAdditions(for: fileBrowserItem, new: newChildren)
|
||||
self.handleChildren(for: fileBrowserItem, new: newChildren)
|
||||
fileBrowserItem.children.filter { self.isItemExpanded($0) }.forEach(self.update)
|
||||
}
|
||||
|
||||
fileprivate func fileBrowserItem(with url: URL) -> FileBrowserItem? {
|
||||
@ -234,7 +222,7 @@ extension FileOutlineView {
|
||||
func outlineView(_: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
||||
if item == nil {
|
||||
if self.root.isChildrenScanned == false {
|
||||
self.root.children = sortedChildren(of: self.cwd)
|
||||
self.root.children = self.sortedChildren(of: self.cwd)
|
||||
self.root.isChildrenScanned = true
|
||||
}
|
||||
|
||||
|
62
bin/generate_source.py
Executable file
62
bin/generate_source.py
Executable file
@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import io
|
||||
import re
|
||||
from string import Template
|
||||
|
||||
if 'CONFIGURATION' in os.environ and os.environ['CONFIGURATION'] == 'Debug':
|
||||
if os.path.isfile('SwiftNeoVim/NeoVimAutoCommandEvent.generated.h') and os.path.isfile('SwiftNeoVim/NeoVimAutoCommandEvent.generated.m'):
|
||||
print("Files already there, exiting...")
|
||||
exit(0)
|
||||
|
||||
with io.open('./neovim/build/include/auevents_enum.generated.h', 'r') as auto_cmds_file:
|
||||
raw_auto_cmds = [line.strip() for line in auto_cmds_file.readlines() if re.match(r'^EVENT_', line.strip())]
|
||||
|
||||
|
||||
def convert(line):
|
||||
result = re.match(r'^EVENT_(.*) = (.*)', line.replace(',', ''))
|
||||
return result.group(1), result.group(2)
|
||||
|
||||
|
||||
auto_cmds = [convert(line) for line in raw_auto_cmds]
|
||||
auto_cmds_impl_template = Template(
|
||||
'''
|
||||
@import Foundation;
|
||||
#import "NeoVimAutoCommandEvent.generated.h"
|
||||
|
||||
NSString *neoVimAutoCommandEventName(NeoVimAutoCommandEvent event) {
|
||||
switch (event) {
|
||||
${event_cases}
|
||||
default: return @"NON_EXISTING_EVENT";
|
||||
}
|
||||
}
|
||||
''')
|
||||
|
||||
impl = auto_cmds_impl_template.substitute(
|
||||
event_cases='\n'.join(
|
||||
[' case NeoVimAutoCommandEvent{}: return @"{}";'.format(event[0], event[0]) for event in auto_cmds])
|
||||
)
|
||||
with io.open('SwiftNeoVim/NeoVimAutoCommandEvent.generated.m', 'w') as auto_cmds_impl_file:
|
||||
auto_cmds_impl_file.write(unicode(impl))
|
||||
|
||||
auto_cmds_header_template = Template(
|
||||
'''
|
||||
@import Foundation;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, NeoVimAutoCommandEvent) {
|
||||
${event_cases}
|
||||
};
|
||||
|
||||
#define NumberOfAutoCommandEvents ${count}
|
||||
extern NSString * __nonnull neoVimAutoCommandEventName(NeoVimAutoCommandEvent event);
|
||||
''')
|
||||
|
||||
header = auto_cmds_header_template.substitute(
|
||||
event_cases='\n'.join(
|
||||
[' NeoVimAutoCommandEvent{} = {},'.format(event[0], event[1]) for event in auto_cmds]
|
||||
),
|
||||
count=str(len(auto_cmds))
|
||||
)
|
||||
with io.open('SwiftNeoVim/NeoVimAutoCommandEvent.generated.h', 'w') as auto_cmds_header_file:
|
||||
auto_cmds_header_file.write(unicode(header))
|
Loading…
Reference in New Issue
Block a user