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

GH-215 Use one file for NeoVimView

- This way we can use more `private` properties.
This commit is contained in:
Tae Won Ha 2016-07-16 00:07:50 +02:00
parent 244063172e
commit a06c1ca4f6
No known key found for this signature in database
GPG Key ID: E40743465B5B8B44
5 changed files with 625 additions and 651 deletions

View File

@ -22,30 +22,31 @@ public class NeoVimView: NSView {
public let uuid = NSUUID().UUIDString
public var delegate: NeoVimViewDelegate?
let agent: NeoVimAgent
private let agent: NeoVimAgent
let grid = Grid()
private let grid = Grid()
private var markedText: String?
var markedText: String?
/// 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?
private var lastMarkedText: String?
var markedPosition = Position.null
var keyDownDone = true
private var markedPosition = Position.null
private var keyDownDone = true
var lastClickedCellPosition = Position.null
private var lastClickedCellPosition = Position.null
var xOffset = CGFloat(0)
var yOffset = CGFloat(0)
var cellSize = CGSize.zero
var descent = CGFloat(0)
var leading = CGFloat(0)
private var xOffset = CGFloat(0)
private var yOffset = CGFloat(0)
private var cellSize = CGSize.zero
private var descent = CGFloat(0)
private var leading = CGFloat(0)
var scrollGuardCounterX = 9
var scrollGuardCounterY = 9
let scrollGuardYield = 10
private var scrollGuardCounterX = 9
private var scrollGuardCounterY = 9
private let scrollGuardYield = 10
private let drawer: TextDrawer
private var font: NSFont {
@ -119,7 +120,7 @@ public class NeoVimView: NSView {
self.resizeNeoVimUiTo(size: self.bounds.size)
}
func resizeNeoVimUiTo(size size: CGSize) {
private func resizeNeoVimUiTo(size size: CGSize) {
// NSLog("\(#function): \(size)")
let discreteSize = Size(width: Int(floor(size.width / self.cellSize.width)),
height: Int(floor(size.height / self.cellSize.height)))
@ -250,22 +251,22 @@ public class NeoVimView: NSView {
return Region(top: rowStart, bottom: rowEnd, left: columnStart, right: columnEnd)
}
func pointInViewFor(position position: Position) -> CGPoint {
private func pointInViewFor(position position: Position) -> CGPoint {
return self.pointInViewFor(row: position.row, column: position.column)
}
func pointInViewFor(row row: Int, column: Int) -> CGPoint {
private func pointInViewFor(row row: Int, column: Int) -> CGPoint {
return CGPoint(
x: CGFloat(column) * self.cellSize.width + self.xOffset,
y: self.frame.size.height - CGFloat(row) * self.cellSize.height - self.cellSize.height - self.yOffset
)
}
func cellRectFor(row row: Int, column: Int) -> CGRect {
private func cellRectFor(row row: Int, column: Int) -> CGRect {
return CGRect(origin: self.pointInViewFor(row: row, column: column), size: self.cellSize)
}
func regionRectFor(region region: Region) -> CGRect {
private func regionRectFor(region region: Region) -> CGRect {
let top = CGFloat(region.top)
let bottom = CGFloat(region.bottom)
let left = CGFloat(region.left)
@ -282,11 +283,11 @@ public class NeoVimView: NSView {
)
}
func wrapNamedKeys(string: String) -> String {
private func wrapNamedKeys(string: String) -> String {
return "<\(string)>"
}
func vimPlainString(string: String) -> String {
private func vimPlainString(string: String) -> String {
return string.stringByReplacingOccurrencesOfString("<", withString: self.wrapNamedKeys("lt"))
}
@ -294,3 +295,605 @@ public class NeoVimView: NSView {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - Key Events
extension NeoVimView: NSTextInputClient {
override public func keyDown(event: NSEvent) {
self.keyDownDone = false
let context = NSTextInputContext.currentInputContext()!
let cocoaHandledEvent = context.handleEvent(event)
if self.keyDownDone && cocoaHandledEvent {
return
}
// NSLog("\(#function): \(event)")
let modifierFlags = event.modifierFlags
let capslock = modifierFlags.contains(.AlphaShiftKeyMask)
let shift = modifierFlags.contains(.ShiftKeyMask)
let chars = event.characters!
let charsIgnoringModifiers = shift || capslock ? event.charactersIgnoringModifiers!.lowercaseString
: 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: AnyObject, replacementRange: NSRange) {
// NSLog("\(#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 doCommandBySelector(aSelector: Selector) {
// NSLog("\(#function): \(aSelector)");
// FIXME: handle when -> delete
if self.respondsToSelector(aSelector) {
Swift.print("\(#function): calling \(aSelector)")
self.performSelector(aSelector, withObject: self)
self.keyDownDone = true
return
}
// NSLog("\(#function): "\(aSelector) not implemented, forwarding input to vim")
self.keyDownDone = false
}
public func setMarkedText(aString: AnyObject, 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(aString) // should not occur
}
// NSLog("\(#function): \(self.markedText), \(selectedRange), \(replacementRange)")
self.agent.vimInputMarkedText(self.markedText!)
self.keyDownDone = true
}
public func unmarkText() {
// NSLog("\(#function): ")
self.markedText = nil
self.markedPosition = Position.null
self.keyDownDone = true
// TODO: necessary?
self.setNeedsDisplayInRect(self.cellRectFor(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 {
// NSLog("\(#function): not found")
return NSRange(location: NSNotFound, length: 0)
}
let result = NSRange(location: self.grid.singleIndexFrom(self.grid.putPosition), length: 0)
// NSLog("\(#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)
// NSLog("\(#function): \(result)")
return result
}
NSLog("\(#function): returning empty range")
return NSRange(location: NSNotFound, length: 0)
}
public func hasMarkedText() -> Bool {
// NSLog("\(#function)")
return self.markedText != nil
}
// FIXME: take into account the "return nil"-case
// FIXME: just fix me, PLEASE...
public func attributedSubstringForProposedRange(aRange: NSRange, actualRange: NSRangePointer) -> NSAttributedString? {
// NSLog("\(#function): \(aRange), \(actualRange[0])")
if aRange.location == NSNotFound {
// NSLog("\(#function): range not found: returning nil")
return nil
}
guard let lastMarkedText = self.lastMarkedText else {
// NSLog("\(#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 + " " }
// NSLog("\(#function): \(aRange), \(actualRange[0]): \(fillChars + lastMarkedText)")
return NSAttributedString(string: fillChars + lastMarkedText)
}
public func validAttributesForMarkedText() -> [String] {
return []
}
public func firstRectForCharacterRange(aRange: NSRange, actualRange: NSRangePointer) -> NSRect {
let position = self.grid.positionFromSingleIndex(aRange.location)
// NSLog("\(#function): \(aRange),\(actualRange[0]) -> \(position.row):\(position.column)")
let resultInSelf = self.cellRectFor(row: position.row, column: position.column)
let result = self.window?.convertRectToScreen(self.convertRect(resultInSelf, toView: nil))
return result!
}
public func characterIndexForPoint(aPoint: NSPoint) -> Int {
// NSLog("\(#function): \(aPoint)")
return 1
}
private func vimModifierFlags(modifierFlags: NSEventModifierFlags) -> String? {
var result = ""
let control = modifierFlags.contains(.ControlKeyMask)
let option = modifierFlags.contains(.AlternateKeyMask)
let command = modifierFlags.contains(.CommandKeyMask)
if control {
result += "C-"
}
if option {
result += "M-"
}
if command {
result += "D-"
}
if result.characters.count > 0 {
return result
}
return nil
}
}
// MARK: - Mouse Events
extension NeoVimView {
override public func mouseDown(event: NSEvent) {
self.mouse(event: event, vimName:"LeftMouse")
}
override public func mouseUp(event: NSEvent) {
self.mouse(event: event, vimName:"LeftRelease")
}
override public func mouseDragged(event: NSEvent) {
self.mouse(event: event, vimName:"LeftDrag")
}
override public func scrollWheel(event: NSEvent) {
let (deltaX, deltaY) = (event.scrollingDeltaX, event.scrollingDeltaY)
if deltaX == 0 && deltaY == 0 {
return
}
let cellPosition = self.cellPositionFor(event: event)
let (vimInputX, vimInputY) = self.vimScrollInputFor(deltaX: deltaX, deltaY: deltaY,
modifierFlags: event.modifierFlags,
cellPosition: cellPosition)
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 / 20), 25)) : 0
let numY = deltaY != 0 ? max(1, min(Int(absDeltaY / 20), 25)) : 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)
}
}
}
private func cellPositionFor(event event: NSEvent) -> Position {
let location = self.convertPoint(event.locationInWindow, fromView: nil)
let cellPosition = Position(
row: min(Int(floor(location.x / self.cellSize.width)), self.grid.size.width - 1),
column: min(Int(floor((self.bounds.height - location.y) / self.cellSize.height)), self.grid.size.height - 1)
)
return cellPosition
}
private func mouse(event 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
}
// NSLog("\(#function): \(result)")
self.agent.vimInput(result)
}
private func shouldFireVimInputFor(event 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
}
private func vimClickCountFrom(event 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 ""
}
}
private func vimScrollEventNamesFor(deltaX 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)
}
private func vimScrollInputFor(deltaX 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)
}
private 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)
}
}
private 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)
}
}
}
// MARK: - NeoVimUiBridgeProtocol
extension NeoVimView: NeoVimUiBridgeProtocol {
public func neoVimUiIsReady() {
DispatchUtils.gui {
self.resizeNeoVimUiTo(size: self.frame.size)
}
}
public func resizeToWidth(width: Int32, height: Int32) {
DispatchUtils.gui {
NSLog("\(#function): \(width):\(height)")
self.grid.resize(Size(width: Int(width), height: Int(height)))
self.needsDisplay = true
}
}
public func clear() {
DispatchUtils.gui {
self.grid.clear()
self.needsDisplay = true
}
}
public func eolClear() {
DispatchUtils.gui {
self.grid.eolClear()
let origin = self.pointInViewFor(position: self.grid.putPosition)
let size = CGSize(
width: CGFloat(self.grid.region.right - self.grid.putPosition.column + 1) * self.cellSize.width,
height: self.cellSize.height
)
let rect = CGRect(origin: origin, size: size)
self.setNeedsDisplayInRect(rect)
}
}
public func gotoPosition(position: Position, screenCursor: Position) {
DispatchUtils.gui {
// NSLog("\(#function): \(position), \(screenCursor)")
// Because neovim fills blank space with "Space" and when we enter "Space" we don't get the puts.
self.setNeedsDisplay(cellPosition: self.grid.putPosition)
self.setNeedsDisplay(cellPosition: self.grid.nextCellPosition(self.grid.putPosition))
self.setNeedsDisplay(screenCursor: position)
self.grid.goto(position)
self.grid.moveCursor(screenCursor)
}
}
public func updateMenu() {
}
public func busyStart() {
}
public func busyStop() {
}
public func mouseOn() {
}
public func mouseOff() {
}
public func modeChange(mode: Int32) {
}
public func setScrollRegionToTop(top: Int32, bottom: Int32, left: Int32, right: Int32) {
DispatchUtils.gui {
let region = Region(top: Int(top), bottom: Int(bottom), left: Int(left), right: Int(right))
self.grid.setScrollRegion(region)
self.setNeedsDisplay(region: region)
}
}
public func scroll(count: Int32) {
DispatchUtils.gui {
self.grid.scroll(Int(count))
self.setNeedsDisplay(region: self.grid.region)
}
}
public func highlightSet(attrs: CellAttributes) {
DispatchUtils.gui {
self.grid.attrs = attrs
}
}
public func put(string: String) {
DispatchUtils.gui {
let curPos = self.grid.putPosition
// NSLog("\(#function): \(curPos) -> \(string)")
self.grid.put(string)
self.setNeedsDisplay(cellPosition: curPos)
// When the cursor is in the command line, then we need this...
self.setNeedsDisplay(cellPosition: self.grid.nextCellPosition(curPos))
self.setNeedsDisplay(screenCursor: self.grid.screenCursor)
}
}
public func putMarkedText(markedText: String) {
DispatchUtils.gui {
NSLog("\(#function): '\(markedText)'")
let curPos = self.grid.putPosition
self.grid.putMarkedText(markedText)
self.setNeedsDisplay(position: curPos)
// When the cursor is in the command line, then we need this...
self.setNeedsDisplay(cellPosition: self.grid.nextCellPosition(curPos))
if markedText.characters.count == 0 {
self.setNeedsDisplay(position: self.grid.previousCellPosition(curPos))
}
self.setNeedsDisplay(screenCursor: self.grid.screenCursor)
}
}
public func unmarkRow(row: Int32, column: Int32) {
DispatchUtils.gui {
let position = Position(row: Int(row), column: Int(column))
NSLog("\(#function): \(position)")
self.grid.unmarkCell(position)
self.setNeedsDisplay(position: position)
self.setNeedsDisplay(screenCursor: self.grid.screenCursor)
}
}
public func bell() {
DispatchUtils.gui {
NSBeep()
}
}
public func visualBell() {
}
public func flush() {
// NSLog("\(#function)")
}
public func updateForeground(fg: Int32) {
DispatchUtils.gui {
self.grid.foreground = UInt32(bitPattern: fg)
}
}
public func updateBackground(bg: Int32) {
DispatchUtils.gui {
self.grid.background = UInt32(bitPattern: bg)
self.layer?.backgroundColor = ColorUtils.colorIgnoringAlpha(self.grid.background).CGColor
}
}
public func updateSpecial(sp: Int32) {
DispatchUtils.gui {
self.grid.special = UInt32(bitPattern: sp)
}
}
public func suspend() {
}
public func setTitle(title: String) {
DispatchUtils.gui {
self.delegate?.setNeoVimTitle(title)
}
}
public func setIcon(icon: String) {
}
public func stop() {
}
private func setNeedsDisplay(region region: Region) {
self.setNeedsDisplayInRect(self.regionRectFor(region: region))
}
private func setNeedsDisplay(cellPosition position: Position) {
self.setNeedsDisplay(position: position)
if self.grid.isCellEmpty(position) {
self.setNeedsDisplay(position: self.grid.previousCellPosition(position))
}
if self.grid.isNextCellEmpty(position) {
self.setNeedsDisplay(position: self.grid.nextCellPosition(position))
}
}
private func setNeedsDisplay(position position: Position) {
self.setNeedsDisplay(row: position.row, column: position.column)
}
private func setNeedsDisplay(row row: Int, column: Int) {
// Swift.print("\(#function): \(row):\(column)")
self.setNeedsDisplayInRect(self.cellRectFor(row: row, column: column))
}
private func setNeedsDisplay(screenCursor position: Position) {
self.setNeedsDisplay(position: position)
if self.grid.isNextCellEmpty(position) {
self.setNeedsDisplay(position: self.grid.nextCellPosition(position))
}
}
}

View File

@ -1,219 +0,0 @@
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
extension NeoVimView: NSTextInputClient {
override public func keyDown(event: NSEvent) {
self.keyDownDone = false
let context = NSTextInputContext.currentInputContext()!
let cocoaHandledEvent = context.handleEvent(event)
if self.keyDownDone && cocoaHandledEvent {
return
}
// NSLog("\(#function): \(event)")
let modifierFlags = event.modifierFlags
let capslock = modifierFlags.contains(.AlphaShiftKeyMask)
let shift = modifierFlags.contains(.ShiftKeyMask)
let chars = event.characters!
let charsIgnoringModifiers = shift || capslock ? event.charactersIgnoringModifiers!.lowercaseString
: 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: AnyObject, replacementRange: NSRange) {
// NSLog("\(#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 doCommandBySelector(aSelector: Selector) {
// NSLog("\(#function): \(aSelector)");
// FIXME: handle when -> delete
if self.respondsToSelector(aSelector) {
Swift.print("\(#function): calling \(aSelector)")
self.performSelector(aSelector, withObject: self)
self.keyDownDone = true
return
}
// NSLog("\(#function): "\(aSelector) not implemented, forwarding input to vim")
self.keyDownDone = false
}
public func setMarkedText(aString: AnyObject, 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(aString) // should not occur
}
// NSLog("\(#function): \(self.markedText), \(selectedRange), \(replacementRange)")
self.agent.vimInputMarkedText(self.markedText!)
self.keyDownDone = true
}
public func unmarkText() {
// NSLog("\(#function): ")
self.markedText = nil
self.markedPosition = Position.null
self.keyDownDone = true
// TODO: necessary?
self.setNeedsDisplayInRect(self.cellRectFor(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 {
// NSLog("\(#function): not found")
return NSRange(location: NSNotFound, length: 0)
}
let result = NSRange(location: self.grid.singleIndexFrom(self.grid.putPosition), length: 0)
// NSLog("\(#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)
// NSLog("\(#function): \(result)")
return result
}
NSLog("\(#function): returning empty range")
return NSRange(location: NSNotFound, length: 0)
}
public func hasMarkedText() -> Bool {
// NSLog("\(#function)")
return self.markedText != nil
}
// FIXME: take into account the "return nil"-case
// FIXME: just fix me, PLEASE...
public func attributedSubstringForProposedRange(aRange: NSRange, actualRange: NSRangePointer) -> NSAttributedString? {
// NSLog("\(#function): \(aRange), \(actualRange[0])")
if aRange.location == NSNotFound {
// NSLog("\(#function): range not found: returning nil")
return nil
}
guard let lastMarkedText = self.lastMarkedText else {
// NSLog("\(#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 + " " }
// NSLog("\(#function): \(aRange), \(actualRange[0]): \(fillChars + lastMarkedText)")
return NSAttributedString(string: fillChars + lastMarkedText)
}
public func validAttributesForMarkedText() -> [String] {
return []
}
public func firstRectForCharacterRange(aRange: NSRange, actualRange: NSRangePointer) -> NSRect {
let position = self.grid.positionFromSingleIndex(aRange.location)
// NSLog("\(#function): \(aRange),\(actualRange[0]) -> \(position.row):\(position.column)")
let resultInSelf = self.cellRectFor(row: position.row, column: position.column)
let result = self.window?.convertRectToScreen(self.convertRect(resultInSelf, toView: nil))
return result!
}
public func characterIndexForPoint(aPoint: NSPoint) -> Int {
// NSLog("\(#function): \(aPoint)")
return 1
}
func vimModifierFlags(modifierFlags: NSEventModifierFlags) -> String? {
var result = ""
let control = modifierFlags.contains(.ControlKeyMask)
let option = modifierFlags.contains(.AlternateKeyMask)
let command = modifierFlags.contains(.CommandKeyMask)
if control {
result += "C-"
}
if option {
result += "M-"
}
if command {
result += "D-"
}
if result.characters.count > 0 {
return result
}
return nil
}
}

View File

@ -1,180 +0,0 @@
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
extension NeoVimView {
override public func mouseDown(event: NSEvent) {
self.mouse(event: event, vimName:"LeftMouse")
}
override public func mouseUp(event: NSEvent) {
self.mouse(event: event, vimName:"LeftRelease")
}
override public func mouseDragged(event: NSEvent) {
self.mouse(event: event, vimName:"LeftDrag")
}
override public func scrollWheel(event: NSEvent) {
let (deltaX, deltaY) = (event.scrollingDeltaX, event.scrollingDeltaY)
if deltaX == 0 && deltaY == 0 {
return
}
let cellPosition = self.cellPositionFor(event: event)
let (vimInputX, vimInputY) = self.vimScrollInputFor(deltaX: deltaX, deltaY: deltaY,
modifierFlags: event.modifierFlags,
cellPosition: cellPosition)
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 / 20), 25)) : 0
let numY = deltaY != 0 ? max(1, min(Int(absDeltaY / 20), 25)) : 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)
}
}
}
private func cellPositionFor(event event: NSEvent) -> Position {
let location = self.convertPoint(event.locationInWindow, fromView: nil)
let cellPosition = Position(
row: min(Int(floor(location.x / self.cellSize.width)), self.grid.size.width - 1),
column: min(Int(floor((self.bounds.height - location.y) / self.cellSize.height)), self.grid.size.height - 1)
)
return cellPosition
}
private func mouse(event 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
}
// NSLog("\(#function): \(result)")
self.agent.vimInput(result)
}
private func shouldFireVimInputFor(event 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
}
private func vimClickCountFrom(event 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 ""
}
}
private func vimScrollEventNamesFor(deltaX 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)
}
private func vimScrollInputFor(deltaX 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)
}
private 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)
}
}
private 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)
}
}
}

View File

@ -1,218 +0,0 @@
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
extension NeoVimView: NeoVimUiBridgeProtocol {
public func neoVimUiIsReady() {
DispatchUtils.gui {
self.resizeNeoVimUiTo(size: self.frame.size)
}
}
public func resizeToWidth(width: Int32, height: Int32) {
DispatchUtils.gui {
NSLog("\(#function): \(width):\(height)")
self.grid.resize(Size(width: Int(width), height: Int(height)))
self.needsDisplay = true
}
}
public func clear() {
DispatchUtils.gui {
self.grid.clear()
self.needsDisplay = true
}
}
public func eolClear() {
DispatchUtils.gui {
self.grid.eolClear()
let origin = self.pointInViewFor(position: self.grid.putPosition)
let size = CGSize(
width: CGFloat(self.grid.region.right - self.grid.putPosition.column + 1) * self.cellSize.width,
height: self.cellSize.height
)
let rect = CGRect(origin: origin, size: size)
self.setNeedsDisplayInRect(rect)
}
}
public func gotoPosition(position: Position, screenCursor: Position) {
DispatchUtils.gui {
// NSLog("\(#function): \(position), \(screenCursor)")
// Because neovim fills blank space with "Space" and when we enter "Space" we don't get the puts.
self.setNeedsDisplay(cellPosition: self.grid.putPosition)
self.setNeedsDisplay(cellPosition: self.grid.nextCellPosition(self.grid.putPosition))
self.setNeedsDisplay(screenCursor: position)
self.grid.goto(position)
self.grid.moveCursor(screenCursor)
}
}
public func updateMenu() {
}
public func busyStart() {
}
public func busyStop() {
}
public func mouseOn() {
}
public func mouseOff() {
}
public func modeChange(mode: Int32) {
}
public func setScrollRegionToTop(top: Int32, bottom: Int32, left: Int32, right: Int32) {
DispatchUtils.gui {
let region = Region(top: Int(top), bottom: Int(bottom), left: Int(left), right: Int(right))
self.grid.setScrollRegion(region)
self.setNeedsDisplay(region: region)
}
}
public func scroll(count: Int32) {
DispatchUtils.gui {
self.grid.scroll(Int(count))
self.setNeedsDisplay(region: self.grid.region)
}
}
public func highlightSet(attrs: CellAttributes) {
DispatchUtils.gui {
self.grid.attrs = attrs
}
}
public func put(string: String) {
DispatchUtils.gui {
let curPos = self.grid.putPosition
// NSLog("\(#function): \(curPos) -> \(string)")
self.grid.put(string)
self.setNeedsDisplay(cellPosition: curPos)
// When the cursor is in the command line, then we need this...
self.setNeedsDisplay(cellPosition: self.grid.nextCellPosition(curPos))
self.setNeedsDisplay(screenCursor: self.grid.screenCursor)
}
}
public func putMarkedText(markedText: String) {
DispatchUtils.gui {
NSLog("\(#function): '\(markedText)'")
let curPos = self.grid.putPosition
self.grid.putMarkedText(markedText)
self.setNeedsDisplay(position: curPos)
// When the cursor is in the command line, then we need this...
self.setNeedsDisplay(cellPosition: self.grid.nextCellPosition(curPos))
if markedText.characters.count == 0 {
self.setNeedsDisplay(position: self.grid.previousCellPosition(curPos))
}
self.setNeedsDisplay(screenCursor: self.grid.screenCursor)
}
}
public func unmarkRow(row: Int32, column: Int32) {
DispatchUtils.gui {
let position = Position(row: Int(row), column: Int(column))
NSLog("\(#function): \(position)")
self.grid.unmarkCell(position)
self.setNeedsDisplay(position: position)
self.setNeedsDisplay(screenCursor: self.grid.screenCursor)
}
}
public func bell() {
DispatchUtils.gui {
NSBeep()
}
}
public func visualBell() {
}
public func flush() {
// NSLog("\(#function)")
}
public func updateForeground(fg: Int32) {
DispatchUtils.gui {
self.grid.foreground = UInt32(bitPattern: fg)
}
}
public func updateBackground(bg: Int32) {
DispatchUtils.gui {
self.grid.background = UInt32(bitPattern: bg)
self.layer?.backgroundColor = ColorUtils.colorIgnoringAlpha(self.grid.background).CGColor
}
}
public func updateSpecial(sp: Int32) {
DispatchUtils.gui {
self.grid.special = UInt32(bitPattern: sp)
}
}
public func suspend() {
}
public func setTitle(title: String) {
DispatchUtils.gui {
self.delegate?.setNeoVimTitle(title)
}
}
public func setIcon(icon: String) {
}
public func stop() {
}
private func setNeedsDisplay(region region: Region) {
self.setNeedsDisplayInRect(self.regionRectFor(region: region))
}
private func setNeedsDisplay(cellPosition position: Position) {
self.setNeedsDisplay(position: position)
if self.grid.isCellEmpty(position) {
self.setNeedsDisplay(position: self.grid.previousCellPosition(position))
}
if self.grid.isNextCellEmpty(position) {
self.setNeedsDisplay(position: self.grid.nextCellPosition(position))
}
}
private func setNeedsDisplay(position position: Position) {
self.setNeedsDisplay(row: position.row, column: position.column)
}
private func setNeedsDisplay(row row: Int, column: Int) {
// Swift.print("\(#function): \(row):\(column)")
self.setNeedsDisplayInRect(self.cellRectFor(row: row, column: column))
}
private func setNeedsDisplay(screenCursor position: Position) {
self.setNeedsDisplay(position: position)
if self.grid.isNextCellEmpty(position) {
self.setNeedsDisplay(position: self.grid.nextCellPosition(position))
}
}
}

View File

@ -35,7 +35,6 @@
4B9A15241D2993DA009F9F67 /* Nimble.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B56F29B1D29926600C1F92E /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4B9A15251D2993DA009F9F67 /* Quick.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B56F29C1D29926600C1F92E /* Quick.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4B9A15261D2993DF009F9F67 /* SwiftNeoVim.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B2A2BF71D0351810074CE9A /* SwiftNeoVim.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4BC661D41D37F77500EE84F7 /* NeoVimViewMouseEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC661D31D37F77500EE84F7 /* NeoVimViewMouseEvents.swift */; };
4BCADE081D11ED12004DAD0F /* CocoaExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCADE071D11ED12004DAD0F /* CocoaExtensions.swift */; };
4BD3BF931D32A95800082605 /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD3BF921D32A95800082605 /* MainWindowController.swift */; };
4BD3BF971D32B0DB00082605 /* MainWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD3BF961D32B0DB00082605 /* MainWindowManager.swift */; };
@ -59,10 +58,8 @@
4BEBA50B1CFF374B00673FDF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BEBA50A1CFF374B00673FDF /* Assets.xcassets */; };
4BEBA50E1CFF374B00673FDF /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BEBA50C1CFF374B00673FDF /* MainMenu.xib */; };
4BEBA5191CFF374B00673FDF /* VimRTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBA5181CFF374B00673FDF /* VimRTests.swift */; };
4BEE79121D16D0AC0012EDAA /* NeoVimViewUiBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE79111D16D0AC0012EDAA /* NeoVimViewUiBridge.swift */; };
4BEE79151D16D2100012EDAA /* DispatchUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE79141D16D2100012EDAA /* DispatchUtils.swift */; };
4BEE79171D16D3800012EDAA /* CellAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE79161D16D3800012EDAA /* CellAttributes.swift */; };
4BEF363D1D1EC045002A9898 /* NeoVimViewKeyEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF363C1D1EC045002A9898 /* NeoVimViewKeyEvents.swift */; };
4BF6E29C1D34153C0053FA76 /* KeyUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF6E29B1D34153C0053FA76 /* KeyUtils.swift */; };
/* End PBXBuildFile section */
@ -173,7 +170,6 @@
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>"; };
4B97E2CD1D33F53D00FC0660 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainWindow.xib; sourceTree = "<group>"; };
4BC661D31D37F77500EE84F7 /* NeoVimViewMouseEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeoVimViewMouseEvents.swift; sourceTree = "<group>"; };
4BCADE071D11ED12004DAD0F /* CocoaExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CocoaExtensions.swift; sourceTree = "<group>"; };
4BCF638F1D323CFD00F15CE4 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = neovim/src/nvim; sourceTree = SOURCE_ROOT; };
4BD3BF921D32A95800082605 /* MainWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowController.swift; sourceTree = "<group>"; };
@ -202,10 +198,8 @@
4BEBA5181CFF374B00673FDF /* VimRTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VimRTests.swift; sourceTree = "<group>"; };
4BEBA51A1CFF374B00673FDF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4BEBA6621D00157A00673FDF /* runtime */ = {isa = PBXFileReference; lastKnownFileType = folder; name = runtime; path = neovim/runtime; sourceTree = SOURCE_ROOT; };
4BEE79111D16D0AC0012EDAA /* NeoVimViewUiBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeoVimViewUiBridge.swift; sourceTree = "<group>"; };
4BEE79141D16D2100012EDAA /* DispatchUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DispatchUtils.swift; sourceTree = "<group>"; };
4BEE79161D16D3800012EDAA /* CellAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellAttributes.swift; sourceTree = "<group>"; };
4BEF363C1D1EC045002A9898 /* NeoVimViewKeyEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeoVimViewKeyEvents.swift; sourceTree = "<group>"; };
4BF6E29B1D34153C0053FA76 /* KeyUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyUtils.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -409,9 +403,6 @@
isa = PBXGroup;
children = (
1929BF00B466B40629C2AABE /* NeoVimView.swift */,
4BEF363C1D1EC045002A9898 /* NeoVimViewKeyEvents.swift */,
4BC661D31D37F77500EE84F7 /* NeoVimViewMouseEvents.swift */,
4BEE79111D16D0AC0012EDAA /* NeoVimViewUiBridge.swift */,
4B401B191D046E0600D99EDC /* NeoVimViewDelegate.swift */,
4B570DC01D303CAF006EDC21 /* NeoVimAgent.h */,
4B570DC11D303CAF006EDC21 /* NeoVimAgent.m */,
@ -647,13 +638,10 @@
4B2A2C0F1D0353E30074CE9A /* NeoVim.swift in Sources */,
4B401B1A1D046E0600D99EDC /* NeoVimViewDelegate.swift in Sources */,
1929B728262BAA14FC93F6AC /* NeoVimView.swift in Sources */,
4BEF363D1D1EC045002A9898 /* NeoVimViewKeyEvents.swift in Sources */,
4B570DC31D303CAF006EDC21 /* NeoVimAgent.m in Sources */,
4BEE79121D16D0AC0012EDAA /* NeoVimViewUiBridge.swift in Sources */,
4BEE79151D16D2100012EDAA /* DispatchUtils.swift in Sources */,
4BDF641D1D0887C100D47E1D /* TextDrawer.m in Sources */,
4BDF64251D08CAB000D47E1D /* MMCoreTextView.m in Sources */,
4BC661D41D37F77500EE84F7 /* NeoVimViewMouseEvents.swift in Sources */,
1929BEB90DCDAF7A2B68C886 /* ColorUtils.swift in Sources */,
4B4192181D0C52D700A0BEB2 /* Grid.swift in Sources */,
);