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
|
|
|
|
2016-06-27 20:04:27 +03:00
|
|
|
enum Mode {
|
|
|
|
/*
|
|
|
|
#define NORMAL 0x01 /* Normal mode, command expected */
|
|
|
|
#define VISUAL 0x02 /* Visual mode - use get_real_state() */
|
|
|
|
#define OP_PENDING 0x04 /* Normal mode, operator is pending - use
|
|
|
|
get_real_state() */
|
|
|
|
#define CMDLINE 0x08 /* Editing command line */
|
|
|
|
#define INSERT 0x10 /* Insert mode */
|
|
|
|
#define LANGMAP 0x20 /* Language mapping, can be combined with
|
|
|
|
INSERT and CMDLINE */
|
|
|
|
|
|
|
|
#define REPLACE_FLAG 0x40 /* Replace mode flag */
|
|
|
|
#define REPLACE (REPLACE_FLAG + INSERT)
|
|
|
|
# define VREPLACE_FLAG 0x80 /* Virtual-replace mode flag */
|
|
|
|
# define VREPLACE (REPLACE_FLAG + VREPLACE_FLAG + INSERT)
|
|
|
|
#define LREPLACE (REPLACE_FLAG + LANGMAP)
|
|
|
|
|
|
|
|
#define NORMAL_BUSY (0x100 + NORMAL) /* Normal mode, busy with a command */
|
|
|
|
#define HITRETURN (0x200 + NORMAL) /* waiting for return or command */
|
|
|
|
#define ASKMORE 0x300 /* Asking if you want --more-- */
|
|
|
|
#define SETWSIZE 0x400 /* window size has changed */
|
|
|
|
#define ABBREV 0x500 /* abbreviation instead of mapping */
|
|
|
|
#define EXTERNCMD 0x600 /* executing an external command */
|
|
|
|
#define SHOWMATCH (0x700 + INSERT) /* show matching paren */
|
|
|
|
#define CONFIRM 0x800 /* ":confirm" prompt */
|
|
|
|
#define SELECTMODE 0x1000 /* Select mode, only for mappings */
|
|
|
|
#define TERM_FOCUS 0x2000 // Terminal focus mode
|
|
|
|
|
|
|
|
// all mode bits used for mapping
|
|
|
|
#define MAP_ALL_MODES (0x3f | SELECTMODE | TERM_FOCUS)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-06-19 11:07:03 +03:00
|
|
|
/// Contiguous piece of cells of a row that has the same attributes.
|
|
|
|
private struct RowRun: CustomStringConvertible {
|
2016-06-18 12:43:37 +03:00
|
|
|
|
|
|
|
let row: Int
|
|
|
|
let range: Range<Int>
|
|
|
|
let attrs: CellAttributes
|
|
|
|
|
|
|
|
var description: String {
|
2016-06-19 11:07:03 +03:00
|
|
|
return "RowRun<\(row): \(range)\n\(attrs)>"
|
2016-06-18 12:43:37 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-05 10:49:14 +03:00
|
|
|
public class NeoVimView: NSView {
|
|
|
|
|
|
|
|
public var delegate: NeoVimViewDelegate?
|
2016-06-08 19:17:12 +03:00
|
|
|
|
2016-06-18 12:43:37 +03:00
|
|
|
private var font: NSFont {
|
|
|
|
didSet {
|
|
|
|
self.drawer.font = self.font
|
|
|
|
self.cellSize = self.drawer.cellSize
|
2016-06-19 14:39:20 +03:00
|
|
|
self.descent = self.drawer.descent
|
|
|
|
self.leading = self.drawer.leading
|
2016-06-18 12:43:37 +03:00
|
|
|
|
|
|
|
// FIXME: resize and redraw
|
|
|
|
}
|
|
|
|
}
|
2016-06-05 10:49:14 +03:00
|
|
|
|
2016-06-18 12:43:37 +03:00
|
|
|
private let drawer: TextDrawer
|
2016-06-24 22:08:34 +03:00
|
|
|
|
|
|
|
let xpc: NeoVimXpc
|
|
|
|
|
|
|
|
var markedText: String?
|
2016-07-02 22:38:52 +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
|
2016-06-24 22:08:34 +03:00
|
|
|
var keyDownDone = true
|
2016-06-06 19:25:03 +03:00
|
|
|
|
2016-06-19 16:17:43 +03:00
|
|
|
var cellSize = CGSize.zero
|
|
|
|
var descent = CGFloat(0)
|
|
|
|
var leading = CGFloat(0)
|
2016-06-15 00:50:25 +03:00
|
|
|
|
2016-06-19 16:17:43 +03:00
|
|
|
let grid = Grid()
|
2016-06-05 10:49:14 +03:00
|
|
|
|
2016-06-08 19:17:12 +03:00
|
|
|
init(frame rect: NSRect = CGRect.zero, xpc: NeoVimXpc) {
|
2016-06-06 19:25:03 +03:00
|
|
|
self.xpc = xpc
|
2016-06-18 12:43:37 +03:00
|
|
|
|
2016-06-19 14:39:20 +03:00
|
|
|
self.font = NSFont(name: "Menlo", size: 16)!
|
2016-06-18 12:43:37 +03:00
|
|
|
self.drawer = TextDrawer(font: font)
|
|
|
|
|
2016-06-05 10:49:14 +03:00
|
|
|
super.init(frame: rect)
|
2016-06-18 12:43:37 +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-06-05 10:49:14 +03:00
|
|
|
}
|
2016-06-15 23:11:35 +03:00
|
|
|
|
2016-06-27 19:42:37 +03:00
|
|
|
public func debugInfo() {
|
|
|
|
Swift.print(self.grid)
|
|
|
|
}
|
|
|
|
|
2016-06-15 23:11:35 +03:00
|
|
|
override public func drawRect(dirtyUnionRect: NSRect) {
|
|
|
|
guard self.grid.hasData else {
|
|
|
|
return
|
|
|
|
}
|
2016-06-27 19:42:37 +03:00
|
|
|
|
2016-06-29 20:54:06 +03:00
|
|
|
// Swift.print("\(#function): \(dirtyUnionRect)")
|
2016-06-08 19:17:12 +03:00
|
|
|
let context = NSGraphicsContext.currentContext()!.CGContext
|
|
|
|
|
|
|
|
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
|
|
|
|
CGContextSetTextDrawingMode(context, .Fill);
|
|
|
|
|
2016-06-16 19:36:26 +03:00
|
|
|
let dirtyRects = self.rectsBeingDrawn()
|
2016-06-18 12:43:37 +03:00
|
|
|
|
2016-06-19 11:07:03 +03:00
|
|
|
self.rowRunIntersecting(rects: dirtyRects).forEach { rowFrag in
|
2016-06-29 19:32:33 +03:00
|
|
|
// For background drawing we don't filter out the put(0, 0)s: in some cases only the put(0, 0)-cells should be
|
2016-07-01 22:24:42 +03:00
|
|
|
// 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.pointInView(rowFrag.row, column: $0) },
|
2016-06-27 20:04:27 +03:00
|
|
|
background: rowFrag.attrs.background)
|
|
|
|
|
2016-06-08 19:17:12 +03:00
|
|
|
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 }
|
2016-07-01 22:24:42 +03:00
|
|
|
.map { self.pointInView(rowFrag.row, column: $0) }
|
2016-06-15 00:50:25 +03:00
|
|
|
|
2016-06-27 20:04:27 +03:00
|
|
|
if positions.isEmpty {
|
|
|
|
return
|
|
|
|
}
|
2016-06-18 12:43:37 +03:00
|
|
|
|
2016-06-16 19:36:26 +03:00
|
|
|
let string = self.grid.cells[rowFrag.row][rowFrag.range].reduce("") { $0 + $1.string }
|
2016-06-19 14:39:20 +03:00
|
|
|
let glyphPositions = positions.map { CGPoint(x: $0.x, y: $0.y + self.descent + self.leading) }
|
2016-06-18 12:43:37 +03:00
|
|
|
self.drawer.drawString(string,
|
|
|
|
positions: UnsafeMutablePointer(glyphPositions), positionsCount: positions.count,
|
|
|
|
highlightAttrs: rowFrag.attrs,
|
|
|
|
context: context)
|
2016-06-08 19:17:12 +03:00
|
|
|
}
|
2016-06-29 20:54:06 +03:00
|
|
|
|
|
|
|
self.drawCursor(self.grid.background)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func drawCursor(background: UInt32) {
|
|
|
|
// FIXME: for now do some rudimentary cursor drawing
|
2016-07-01 22:24:42 +03:00
|
|
|
let cursorPosition = self.grid.screenCursor
|
2016-06-29 20:54:06 +03:00
|
|
|
// Swift.print("\(#function): \(cursorPosition)")
|
|
|
|
|
|
|
|
var cursorRect = self.cellRect(row: cursorPosition.row, column: cursorPosition.column)
|
2016-06-29 21:06:31 +03:00
|
|
|
if self.grid.isNextCellEmpty(cursorPosition) {
|
|
|
|
let nextPosition = self.grid.nextCellPosition(cursorPosition)
|
|
|
|
cursorRect = cursorRect.union(self.cellRect(row: nextPosition.row, column:nextPosition.column))
|
2016-06-29 20:54:06 +03:00
|
|
|
}
|
|
|
|
|
2016-07-01 22:24:42 +03:00
|
|
|
// set cursor to an abhorrent color for debugging
|
|
|
|
ColorUtils.colorFromCodeIgnoringAlpha(0xFF990000).set()
|
2016-06-29 20:54:06 +03:00
|
|
|
NSRectFillUsingOperation(cursorRect, .CompositeDifference)
|
2016-06-15 23:11:35 +03:00
|
|
|
}
|
2016-06-08 19:17:12 +03:00
|
|
|
|
2016-06-18 12:43:37 +03:00
|
|
|
private func drawBackground(positions positions: [CGPoint], background: UInt32) {
|
|
|
|
ColorUtils.colorFromCodeIgnoringAlpha(background).set()
|
2016-06-16 19:36:26 +03:00
|
|
|
let backgroundRect = CGRect(
|
|
|
|
x: positions[0].x, y: positions[0].y,
|
|
|
|
width: positions.last!.x + self.cellSize.width, height: self.cellSize.height
|
|
|
|
)
|
|
|
|
backgroundRect.fill()
|
|
|
|
}
|
|
|
|
|
2016-06-19 11:07:03 +03:00
|
|
|
private func rowRunIntersecting(rects rects: [CGRect]) -> [RowRun] {
|
2016-06-15 23:11:35 +03:00
|
|
|
return rects
|
|
|
|
.map { rect -> Region in
|
2016-06-19 14:39:20 +03:00
|
|
|
// 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.
|
2016-06-15 23:11:35 +03:00
|
|
|
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
|
2016-06-19 14:39:20 +03:00
|
|
|
|
2016-06-15 23:11:35 +03:00
|
|
|
return Region(top: rowStart, bottom: rowEnd, left: columnStart, right: columnEnd)
|
2016-06-19 14:39:20 +03:00
|
|
|
}
|
2016-06-19 11:07:03 +03:00
|
|
|
.map { region -> [RowRun] in
|
2016-06-19 14:39:20 +03:00
|
|
|
// Map Regions to RowRuns for drawing.
|
|
|
|
return region.rowRange
|
|
|
|
// Map each row in a Region to RowRuns
|
2016-06-19 11:07:03 +03:00
|
|
|
.map { row -> [RowRun] in
|
2016-06-19 14:39:20 +03:00
|
|
|
let columns = region.columnRange
|
2016-06-19 11:07:03 +03:00
|
|
|
let rowCells = self.grid.cells[row]
|
2016-06-19 14:39:20 +03:00
|
|
|
let startIndex = columns.startIndex
|
2016-06-19 11:07:03 +03:00
|
|
|
|
2016-06-19 14:39:20 +03:00
|
|
|
var result = [ RowRun(row: row, range: startIndex...startIndex, attrs: rowCells[startIndex].attrs) ]
|
|
|
|
columns.forEach { idx in
|
2016-06-19 11:07:03 +03:00
|
|
|
if rowCells[idx].attrs == result.last!.attrs {
|
|
|
|
let last = result.popLast()!
|
|
|
|
result.append(RowRun(row: row, range: last.range.startIndex...idx, attrs: last.attrs))
|
|
|
|
} else {
|
|
|
|
result.append(RowRun(row: row, range: idx...idx, attrs: rowCells[idx].attrs))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-19 14:39:20 +03:00
|
|
|
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.
|
|
|
|
} // All RowRuns for all Regions grouped by Region.
|
|
|
|
.flatMap { $0 } // Flattened RowRuns for all Regions.
|
2016-06-05 10:49:14 +03:00
|
|
|
}
|
2016-07-01 22:24:42 +03:00
|
|
|
|
|
|
|
func pointInView(position: Position) -> CGPoint {
|
|
|
|
return self.pointInView(position.row, column: position.column)
|
|
|
|
}
|
2016-06-05 10:49:14 +03:00
|
|
|
|
2016-07-01 22:24:42 +03:00
|
|
|
func pointInView(row: Int, column: Int) -> CGPoint {
|
2016-06-08 19:17:12 +03:00
|
|
|
return CGPoint(
|
|
|
|
x: CGFloat(column) * self.cellSize.width,
|
|
|
|
y: self.frame.size.height - CGFloat(row) * self.cellSize.height - self.cellSize.height
|
|
|
|
)
|
|
|
|
}
|
2016-06-24 22:08:34 +03:00
|
|
|
|
2016-07-01 22:24:42 +03:00
|
|
|
func cellRect(position: Position) -> CGRect {
|
|
|
|
return self.cellRect(row: position.row, column: position.column)
|
|
|
|
}
|
|
|
|
|
2016-06-29 20:54:06 +03:00
|
|
|
func cellRect(row row: Int, column: Int) -> CGRect {
|
2016-07-01 22:24:42 +03:00
|
|
|
return CGRect(origin: self.pointInView(row, column: column), size: self.cellSize)
|
2016-06-24 22:08:34 +03:00
|
|
|
}
|
|
|
|
|
2016-06-27 19:42:37 +03:00
|
|
|
func regionRect(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
|
|
|
|
|
|
|
|
return CGRect(x: left * self.cellSize.width, y: (CGFloat(self.grid.size.height) - bottom) * self.cellSize.height,
|
|
|
|
width: width * self.cellSize.width, height: height * self.cellSize.height)
|
|
|
|
}
|
2016-06-27 20:04:27 +03:00
|
|
|
|
|
|
|
func vimNamedKeys(string: String) -> String {
|
|
|
|
return "<\(string)>"
|
|
|
|
}
|
|
|
|
|
|
|
|
func vimPlainString(string: String) -> String {
|
|
|
|
return string.stringByReplacingOccurrencesOfString("<", withString: self.vimNamedKeys("lt"))
|
|
|
|
}
|
2016-06-27 19:42:37 +03:00
|
|
|
|
2016-06-05 10:49:14 +03:00
|
|
|
required public init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
}
|
2016-06-08 19:17:12 +03:00
|
|
|
|