1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-11-24 11:37:32 +03:00
vimr/SwiftNeoVim/NeoVimView.swift
2016-06-16 18:36:26 +02:00

288 lines
8.2 KiB
Swift

/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
import RxSwift
private struct RowFragment: CustomStringConvertible {
let row: Int
let range: Range<Int>
var description: String {
return "RowFragment<\(row): \(range)>"
}
}
public class NeoVimView: NSView {
public var delegate: NeoVimViewDelegate?
private let qDispatchMainQueue = dispatch_get_main_queue()
private let qLineGap = CGFloat(4)
private var foregroundColor = UInt32(0xFF000000)
private var backgroundColor = UInt32(0xFFFFFFFF)
private var font = NSFont(name: "Menlo", size: 13)!
private let xpc: NeoVimXpc
private let drawer = TextDrawer()
private var cellSize: CGSize = CGSizeMake(0, 0)
private let grid = Grid()
init(frame rect: NSRect = CGRect.zero, xpc: NeoVimXpc) {
self.xpc = xpc
super.init(frame: rect)
self.wantsLayer = true
// hard-code some stuff
let attrs = [ NSFontAttributeName: self.font ]
let width = ceil(" ".sizeWithAttributes(attrs).width)
let height = ceil(self.font.ascender - self.font.descender + self.font.leading) + qLineGap
self.cellSize = CGSize(width: width, height: height)
}
override public func keyDown(theEvent: NSEvent) {
self.xpc.vimInput(theEvent.charactersIgnoringModifiers!)
}
override public func drawRect(dirtyUnionRect: NSRect) {
guard self.grid.hasData else {
return
}
// Swift.print("------- DRAW")
let context = NSGraphicsContext.currentContext()!.CGContext
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextSetTextDrawingMode(context, .Fill);
let dirtyRects = self.rectsBeingDrawn()
self.rowFragmentsIntersecting(rects: dirtyRects).forEach { rowFrag in
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.positionOnView(rowFrag.row, column: $0) }
self.drawBackground(positions: positions)
ColorUtils.colorFromCode(self.foregroundColor).set()
let string = self.grid.cells[rowFrag.row][rowFrag.range].reduce("") { $0 + $1.string }
let glyphPositions = positions.map { CGPoint(x: $0.x, y: $0.y + qLineGap) }
self.drawer.drawString(
string, positions: UnsafeMutablePointer(glyphPositions),
font: self.font, foreground: self.foregroundColor, background: self.backgroundColor,
context: context
)
}
// Swift.print("------- DRAW END")
}
private func drawBackground(positions positions: [CGPoint]) {
ColorUtils.colorFromCode(self.backgroundColor).set()
let backgroundRect = CGRect(
x: positions[0].x, y: positions[0].y,
width: positions.last!.x + self.cellSize.width, height: self.cellSize.height
)
backgroundRect.fill()
}
private func rowFragmentsIntersecting(rects rects: [CGRect]) -> [RowFragment] {
return rects
.map { rect -> Region in
let rowStart = Int(floor((self.frame.height - (rect.origin.y + rect.size.height)) / self.cellSize.height))
let rowEnd = Int(ceil((self.frame.height - rect.origin.y) / self.cellSize.height)) - 1
let columnStart = Int(floor(rect.origin.x / self.cellSize.width))
let columnEnd = Int(ceil((rect.origin.x + rect.size.width) / self.cellSize.width)) - 1
return Region(top: rowStart, bottom: rowEnd, left: columnStart, right: columnEnd)
} // There can be overlaps between the Regions, but for the time being we ignore them.
.map { region -> [RowFragment] in
return (region.rowRange).map { RowFragment(row: $0, range: region.columnRange) }
}
.flatMap { $0 }
}
private func positionOnView(row: Int, column: Int) -> CGPoint {
return CGPoint(
x: CGFloat(column) * self.cellSize.width,
y: self.frame.size.height - CGFloat(row) * self.cellSize.height - self.cellSize.height
)
}
private func gui(call: () -> Void) {
dispatch_async(qDispatchMainQueue, call)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension NeoVimView: NeoVimUiBridgeProtocol {
public func resizeToWidth(width: Int32, height: Int32) {
let rectSize = CGSizeMake(
CGFloat(width) * self.cellSize.width,
CGFloat(height) * self.cellSize.height
)
gui {
Swift.print("### resize to \(width):\(height)")
self.grid.resize(Size(width: Int(width), height: Int(height)))
self.delegate?.resizeToSize(rectSize)
}
}
public func clear() {
gui {
Swift.print("### clear")
self.grid.clear()
self.needsDisplay = true
}
}
public func eolClear() {
gui {
Swift.print("### eol clear")
self.grid.eolClear()
let origin = self.positionOnView(self.grid.position.row, column: self.grid.position.column)
let size = CGSize(
width: CGFloat(self.grid.region.right - self.grid.position.column + 1) * self.cellSize.width,
height: self.cellSize.height
)
let rect = CGRect(origin: origin, size: size)
self.setNeedsDisplayInRect(rect)
}
}
public func cursorGotoRow(row: Int32, column: Int32) {
gui {
Swift.print("### goto: \(row):\(column)")
self.grid.goto(Position(row: Int(row), column: Int(column)))
}
}
public func updateMenu() {
// Swift.print("### update menu")
}
public func busyStart() {
// Swift.print("### busy start")
}
public func busyStop() {
// Swift.print("### busy stop")
}
public func mouseOn() {
// Swift.print("### mouse on")
}
public func mouseOff() {
// Swift.print("### mouse off")
}
public func modeChange(mode: Int32) {
// Swift.print("### mode change to: \(String(format: "%04X", mode))")
}
public func setScrollRegionToTop(top: Int32, bottom: Int32, left: Int32, right: Int32) {
Swift.print("### set scroll region: \(top), \(bottom), \(left), \(right)")
gui {
self.grid.setScrollRegion(Region(top: Int(top), bottom: Int(bottom), left: Int(left), right: Int(right)))
}
}
public func scroll(count: Int32) {
Swift.print("### scroll count: \(count)")
gui {
// Swift.print("before scroll: \(self.grid)")
self.grid.scroll(Int(count))
// Swift.print("after scroll: \(self.grid)")
let top = CGFloat(self.grid.region.top)
let bottom = CGFloat(self.grid.region.bottom)
let left = CGFloat(self.grid.region.left)
let right = CGFloat(self.grid.region.right)
let width = right - left + 1
let height = bottom - top + 1
let rect = CGRect(x: left * self.cellSize.width, y: bottom * self.cellSize.height,
width: width * self.cellSize.width, height: height * self.cellSize.height)
self.setNeedsDisplayInRect(rect)
}
}
public func highlightSet(attrs: HighlightAttributes) {
gui {
// Swift.print("### set highlight")
self.grid.attrs = attrs
}
}
public func put(string: String) {
gui {
let curPos = Position(row: self.grid.position.row, column: self.grid.position.column)
self.grid.put(string)
// Swift.print("### put: \(curPos) -> '\(string)'")
let rect = CGRect(origin: self.positionOnView(curPos.row, column: curPos.column), size: self.cellSize)
self.setNeedsDisplayInRect(rect)
}
}
public func bell() {
gui {
NSBeep()
}
}
public func visualBell() {
// Swift.print("### visual bell")
}
public func flush() {
// gui {
// Swift.print("### flush")
// }
}
public func updateForeground(fg: Int32) {
// Swift.print("### update fg: \(colorFromCode(fg))")
}
public func updateBackground(bg: Int32) {
// Swift.print("### update bg: \(colorFromCode(bg, kind: .Background))")
}
public func updateSpecial(sp: Int32) {
// Swift.print("### update sp: \(colorFromCode(sp, kind: .Special))")
}
public func suspend() {
// Swift.print("### suspend")
}
public func setTitle(title: String) {
// Swift.print("### set title: \(title)")
}
public func setIcon(icon: String) {
// Swift.print("### set icon: \(icon)")
}
public func stop() {
Swift.print("### stop")
}
}