1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-11-28 02:54:31 +03:00
vimr/SwiftNeoVim/NeoVimView.swift

256 lines
9.2 KiB
Swift
Raw Normal View History

/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
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 {
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)>"
}
}
public class NeoVimView: NSView {
public var delegate: NeoVimViewDelegate?
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
// FIXME: resize and redraw
}
}
private let drawer: TextDrawer
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
var keyDownDone = true
2016-06-06 19:25:03 +03:00
var cellSize = CGSize.zero
var descent = CGFloat(0)
var leading = CGFloat(0)
2016-06-15 00:50:25 +03:00
let grid = Grid()
init(frame rect: NSRect = CGRect.zero, xpc: NeoVimXpc) {
2016-06-06 19:25:03 +03:00
self.xpc = xpc
2016-06-19 14:39:20 +03:00
self.font = NSFont(name: "Menlo", size: 16)!
self.drawer = TextDrawer(font: font)
super.init(frame: rect)
self.wantsLayer = true
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-27 19:42:37 +03:00
public func debugInfo() {
Swift.print(self.grid)
}
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)")
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-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) },
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 }
2016-07-01 22:24:42 +03:00
.map { self.pointInView(rowFrag.row, column: $0) }
2016-06-15 00:50:25 +03:00
if positions.isEmpty {
return
}
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) }
self.drawer.drawString(string,
positions: UnsafeMutablePointer(glyphPositions), positionsCount: positions.count,
highlightAttrs: rowFrag.attrs,
context: context)
}
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)
}
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] {
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.
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
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-07-01 22:24:42 +03:00
func pointInView(position: Position) -> CGPoint {
return self.pointInView(position.row, column: position.column)
}
2016-07-01 22:24:42 +03:00
func pointInView(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
)
}
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-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)
}
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
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}