2016-06-05 10:49:14 +03:00
|
|
|
/**
|
|
|
|
* Tae Won Ha - http://taewon.de - @hataewon
|
|
|
|
* See LICENSE
|
|
|
|
*/
|
|
|
|
|
|
|
|
import Cocoa
|
2016-06-18 12:43:37 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
public class NeoVimView: NSView,
|
|
|
|
NeoVimUiBridgeProtocol,
|
|
|
|
NSUserInterfaceValidations,
|
|
|
|
NSTextInputClient {
|
2016-09-24 17:31:14 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
// MARK: - Public
|
2016-09-25 09:55:26 +03:00
|
|
|
public struct Config {
|
2017-04-29 09:11:34 +03:00
|
|
|
|
|
|
|
var useInteractiveZsh: Bool
|
2016-09-24 17:31:14 +03:00
|
|
|
|
2016-09-25 09:55:26 +03:00
|
|
|
public init(useInteractiveZsh: Bool) {
|
|
|
|
self.useInteractiveZsh = useInteractiveZsh
|
|
|
|
}
|
|
|
|
}
|
2016-11-20 14:07:47 +03:00
|
|
|
|
2016-10-07 19:23:54 +03:00
|
|
|
public static let minFontSize = CGFloat(4)
|
|
|
|
public static let maxFontSize = CGFloat(128)
|
2017-05-27 19:10:23 +03:00
|
|
|
public static let defaultFont = NSFont.userFixedPitchFont(ofSize: 12)!
|
2016-10-26 23:59:48 +03:00
|
|
|
public static let defaultLinespacing = CGFloat(1)
|
|
|
|
|
|
|
|
public static let minLinespacing = CGFloat(0.5)
|
|
|
|
public static let maxLinespacing = CGFloat(8)
|
2016-07-14 22:53:20 +03:00
|
|
|
|
2016-10-07 19:23:54 +03:00
|
|
|
public let uuid = UUID().uuidString
|
|
|
|
public weak var delegate: NeoVimViewDelegate?
|
2016-07-30 10:49:39 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
public internal(set) var mode = CursorModeShape.normal
|
2016-11-20 14:07:47 +03:00
|
|
|
|
2016-10-07 19:23:54 +03:00
|
|
|
public var usesLigatures = false {
|
2016-08-21 21:00:29 +03:00
|
|
|
didSet {
|
|
|
|
self.drawer.usesLigatures = self.usesLigatures
|
|
|
|
self.needsDisplay = true
|
|
|
|
}
|
|
|
|
}
|
2016-10-26 23:59:48 +03:00
|
|
|
|
|
|
|
public var linespacing: CGFloat {
|
|
|
|
get {
|
|
|
|
return self._linespacing
|
|
|
|
}
|
|
|
|
|
|
|
|
set {
|
|
|
|
guard newValue >= NeoVimView.minLinespacing && newValue <= NeoVimView.maxLinespacing else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
self._linespacing = newValue
|
|
|
|
self.drawer.linespacing = self.linespacing
|
|
|
|
|
2017-05-01 11:25:03 +03:00
|
|
|
self.updateFontMetaData(self._font)
|
2016-10-26 23:59:48 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-07 19:23:54 +03:00
|
|
|
public var font: NSFont {
|
2016-08-21 21:00:29 +03:00
|
|
|
get {
|
|
|
|
return self._font
|
|
|
|
}
|
|
|
|
|
|
|
|
set {
|
2016-09-25 18:50:33 +03:00
|
|
|
guard newValue.isFixedPitch else {
|
2016-08-21 21:00:29 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let size = newValue.pointSize
|
|
|
|
guard size >= NeoVimView.minFontSize && size <= NeoVimView.maxFontSize else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
self._font = newValue
|
2016-10-26 23:42:49 +03:00
|
|
|
|
2017-05-01 11:25:03 +03:00
|
|
|
self.updateFontMetaData(newValue)
|
2016-08-21 21:00:29 +03:00
|
|
|
}
|
|
|
|
}
|
2016-09-02 19:22:40 +03:00
|
|
|
|
2016-10-07 19:23:54 +03:00
|
|
|
public var cwd: URL {
|
2016-09-02 19:22:40 +03:00
|
|
|
get {
|
2017-05-08 21:25:11 +03:00
|
|
|
return self.agent.pwd()
|
2016-09-02 19:22:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
set {
|
2016-09-25 18:50:33 +03:00
|
|
|
let path = newValue.path
|
2017-03-13 20:44:57 +03:00
|
|
|
guard let escapedCwd = self.agent.escapedFileName(path) else {
|
|
|
|
// this happens when VimR is quitting with some main windows open...
|
2017-05-27 16:22:37 +03:00
|
|
|
self.logger.fault("Escaped file name returned nil.")
|
2017-03-13 20:44:57 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-01-09 23:53:48 +03:00
|
|
|
self.agent.vimCommandOutput("cd \(escapedCwd)")
|
2016-09-02 19:22:40 +03:00
|
|
|
}
|
|
|
|
}
|
2016-09-27 19:02:05 +03:00
|
|
|
|
2016-10-07 19:23:54 +03:00
|
|
|
override public var acceptsFirstResponder: Bool {
|
2016-09-27 19:02:05 +03:00
|
|
|
return true
|
|
|
|
}
|
2016-10-18 23:45:44 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
public internal(set) var currentPosition = Position.beginning
|
2017-01-25 21:24:38 +03:00
|
|
|
|
2016-09-25 09:55:26 +03:00
|
|
|
public init(frame rect: NSRect, config: Config) {
|
2016-10-26 23:42:49 +03:00
|
|
|
self.drawer = TextDrawer(font: self._font)
|
2016-07-10 15:14:02 +03:00
|
|
|
self.agent = NeoVimAgent(uuid: self.uuid)
|
2016-07-14 22:53:20 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
super.init(frame: .zero)
|
2016-09-27 19:02:05 +03:00
|
|
|
|
2016-06-15 23:11:35 +03:00
|
|
|
self.wantsLayer = true
|
2016-06-18 12:43:37 +03:00
|
|
|
self.cellSize = self.drawer.cellSize
|
2016-06-19 14:39:20 +03:00
|
|
|
self.descent = self.drawer.descent
|
|
|
|
self.leading = self.drawer.leading
|
2016-07-09 23:52:59 +03:00
|
|
|
|
|
|
|
// We cannot set bridge in init since self is not available before super.init()...
|
|
|
|
self.agent.bridge = self
|
2016-09-25 09:55:26 +03:00
|
|
|
self.agent.useInteractiveZsh = config.useInteractiveZsh
|
2016-08-25 23:52:31 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
self.launchNeoVim()
|
2017-01-27 17:45:04 +03:00
|
|
|
}
|
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
convenience override public init(frame rect: NSRect) {
|
2016-09-25 09:55:26 +03:00
|
|
|
self.init(frame: rect, config: Config(useInteractiveZsh: false))
|
2016-09-24 17:31:14 +03:00
|
|
|
}
|
|
|
|
|
2016-08-12 15:54:37 +03:00
|
|
|
required public init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
2016-06-15 23:11:35 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
@IBAction public func debug1(_ sender: Any?) {
|
2017-05-27 16:22:37 +03:00
|
|
|
self.logger.debug("DEBUG 1 - Start")
|
2017-01-07 17:12:07 +03:00
|
|
|
self.agent.cursorGo(toRow: 10, column: 5)
|
2017-05-27 16:22:37 +03:00
|
|
|
self.logger.debug("DEBUG 1 - End")
|
2016-08-04 21:01:03 +03:00
|
|
|
}
|
2016-10-26 23:59:48 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
// MARK: - Internal
|
|
|
|
/// Contiguous piece of cells of a row that has the same attributes.
|
|
|
|
struct RowRun: CustomStringConvertible {
|
2016-08-01 08:46:04 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
var row: Int
|
|
|
|
var range: CountableClosedRange<Int>
|
|
|
|
var attrs: CellAttributes
|
2016-07-06 20:02:30 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
var description: String {
|
2017-05-28 21:08:42 +03:00
|
|
|
return "RowRun<\(row): \(range) <- \(attrs)>"
|
2016-08-09 09:22:10 +03:00
|
|
|
}
|
2016-08-04 21:01:03 +03:00
|
|
|
}
|
|
|
|
|
2017-05-27 16:22:37 +03:00
|
|
|
let logger = FileLogger(as: NeoVimView.self, with: URL(fileURLWithPath: "/tmp/nvv.log"))
|
2017-05-28 10:19:33 +03:00
|
|
|
let bridgeLogger = FileLogger(as: NeoVimView.self,
|
2017-05-31 00:57:35 +03:00
|
|
|
with: URL(fileURLWithPath: "/tmp/nvv-bridge.log"),
|
|
|
|
shouldLogDebug: false)
|
2017-05-27 16:13:22 +03:00
|
|
|
let agent: NeoVimAgent
|
|
|
|
let grid = Grid()
|
2016-08-04 21:01:03 +03:00
|
|
|
|
2017-05-28 21:08:42 +03:00
|
|
|
var bufferLayer: CGLayer? {
|
2017-05-27 18:39:58 +03:00
|
|
|
if self._cglayer == nil && self.lockFocusIfCanDraw(),
|
|
|
|
let current = NSGraphicsContext.current()?.cgContext {
|
|
|
|
|
|
|
|
self._cglayer = CGLayer(current, size: self.frame.size.scaling(self.scaleFactor),
|
|
|
|
auxiliaryInfo: nil)
|
|
|
|
self.unlockFocus()
|
|
|
|
}
|
|
|
|
|
|
|
|
return self._cglayer
|
|
|
|
}
|
2017-05-28 21:08:42 +03:00
|
|
|
var bufferContext: CGContext? {
|
2017-05-27 18:39:58 +03:00
|
|
|
if self._cgcontext == nil {
|
2017-05-28 21:08:42 +03:00
|
|
|
self._cgcontext = self.bufferLayer?.context
|
2017-05-27 18:39:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return self._cgcontext
|
|
|
|
}
|
|
|
|
var scaleFactor: CGFloat {
|
|
|
|
return self.window?.screen?.backingScaleFactor ?? 1
|
|
|
|
}
|
|
|
|
|
2017-05-28 21:08:42 +03:00
|
|
|
var rectsToUpdate = Set<CGRect>()
|
2017-05-27 18:39:58 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
let drawer: TextDrawer
|
2016-08-04 21:01:03 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
var markedText: String?
|
2017-01-07 22:17:50 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
/// We store the last marked text because Cocoa's text input system does the following:
|
|
|
|
/// 하 -> hanja popup -> insertText(하) -> attributedSubstring...() -> setMarkedText(下) -> ...
|
|
|
|
/// We want to return "하" in attributedSubstring...()
|
|
|
|
var lastMarkedText: String?
|
|
|
|
|
|
|
|
var markedPosition = Position.null
|
|
|
|
var keyDownDone = true
|
|
|
|
|
|
|
|
var lastClickedCellPosition = Position.null
|
|
|
|
|
|
|
|
var xOffset = CGFloat(0)
|
|
|
|
var yOffset = CGFloat(0)
|
|
|
|
var cellSize = CGSize.zero
|
|
|
|
var descent = CGFloat(0)
|
|
|
|
var leading = CGFloat(0)
|
|
|
|
|
|
|
|
var scrollGuardCounterX = 5
|
|
|
|
var scrollGuardCounterY = 5
|
|
|
|
|
|
|
|
var isCurrentlyPinching = false
|
|
|
|
var pinchTargetScale = CGFloat(1)
|
|
|
|
var pinchBitmap: NSBitmapImageRep?
|
|
|
|
|
|
|
|
var currentlyResizing = false
|
|
|
|
var currentEmoji = "😎"
|
2016-12-05 23:30:25 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
var _font = NeoVimView.defaultFont
|
2016-11-10 22:42:42 +03:00
|
|
|
|
2017-05-28 23:41:22 +03:00
|
|
|
func resetBufferContext() {
|
|
|
|
self._cglayer = nil
|
|
|
|
self._cgcontext = nil
|
|
|
|
}
|
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
// MARK: - Private
|
2017-05-27 18:39:58 +03:00
|
|
|
fileprivate var _cglayer: CGLayer?
|
|
|
|
fileprivate var _cgcontext: CGContext?
|
2017-05-27 16:13:22 +03:00
|
|
|
fileprivate var _linespacing = NeoVimView.defaultLinespacing
|
2017-01-07 22:17:50 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
fileprivate func launchNeoVim() {
|
2017-05-27 17:08:47 +03:00
|
|
|
self.logger.info("=== Starting neovim...")
|
2017-05-28 21:08:42 +03:00
|
|
|
self.bridgeLogger.info("=== Starting neovim...")
|
2017-05-27 16:13:22 +03:00
|
|
|
let noErrorDuringInitialization = self.agent.runLocalServerAndNeoVim()
|
2016-08-09 09:22:10 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
// Neovim is ready now: resize neovim to bounds.
|
|
|
|
self.agent.vimCommand("set mouse=a")
|
|
|
|
self.agent.setBoolOption("title", to: true)
|
|
|
|
self.agent.setBoolOption("termguicolors", to: true)
|
2016-08-09 09:22:10 +03:00
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
if noErrorDuringInitialization == false {
|
2017-05-27 17:08:47 +03:00
|
|
|
self.logger.fault("There was an error launching neovim.")
|
|
|
|
|
2017-05-27 16:13:22 +03:00
|
|
|
let alert = NSAlert()
|
|
|
|
alert.alertStyle = .warning
|
|
|
|
alert.messageText = "Error during initialization"
|
|
|
|
alert.informativeText = "There was an error during the initialization of NeoVim. " +
|
|
|
|
"Use :messages to view the error messages."
|
|
|
|
alert.runModal()
|
2016-08-04 21:01:03 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|