1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-26 23:36:08 +03:00
vimr/SwiftNeoVim/Grid.swift

268 lines
7.4 KiB
Swift
Raw Normal View History

/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Foundation
struct Cell: CustomStringConvertible {
private let attributes: CellAttributes
let string: String
var marked: Bool
var attrs: CellAttributes {
2016-07-03 22:59:03 +03:00
return self.marked ? self.attributes.inverted : self.attributes
}
init(string: String, attrs: CellAttributes, marked: Bool = false) {
self.string = string
self.attributes = attrs
self.marked = marked
}
var description: String {
return self.string.characters.count > 0 ? self.string : "*"
}
}
2016-07-01 22:24:42 +03:00
extension Position: CustomStringConvertible {
static let zero = Position(row: 0, column: 0)
static let null = Position(row: -1, column: -1)
2016-07-01 22:24:42 +03:00
public var description: String {
return "Position<\(self.row):\(self.column)>"
}
}
2016-07-14 00:08:45 +03:00
func == (left: Position, right: Position) -> Bool {
if left.row != right.row { return false }
if left.column != right.column { return false }
return true
}
func != (left: Position, right: Position) -> Bool {
return !(left == right)
}
struct Size: CustomStringConvertible {
static let zero = Size(width: 0, height: 0)
let width: Int
let height: Int
var description: String {
return "Size<\(self.width):\(self.height)>"
}
}
struct Region: CustomStringConvertible {
static let zero = Region(top: 0, bottom: 0, left: 0, right: 0)
let top: Int
let bottom: Int
let left: Int
let right: Int
var description: String {
return "Region<\(self.top)...\(self.bottom):\(self.left)...\(self.right)>"
}
var rowRange: Range<Int> {
return self.top...self.bottom
}
var columnRange: Range<Int> {
return self.left...self.right
}
}
2016-06-16 19:36:26 +03:00
/// Almost a verbatim copy of ugrid.c of NeoVim
class Grid: CustomStringConvertible {
private(set) var region = Region.zero
private(set) var size = Size.zero
2016-07-01 22:24:42 +03:00
private(set) var putPosition = Position.zero
private(set) var screenCursor = Position.zero
var foreground = qDefaultForeground
var background = qDefaultBackground
var special = qDefaultSpecial
var attrs: CellAttributes = CellAttributes(
fontTrait: .None,
foreground: qDefaultForeground, background: qDefaultBackground, special: qDefaultSpecial
2016-06-19 22:11:03 +03:00
)
private(set) var cells: [[Cell]] = []
var hasData: Bool {
return !self.cells.isEmpty
}
var description: String {
return self.cells.reduce("<<< Grid\n") { $1.reduce($0) { $0 + $1.description } + "\n" } + ">>>"
}
func resize(size: Size) {
self.region = Region(top: 0, bottom: size.height - 1, left: 0, right: size.width - 1)
self.size = size
2016-07-01 22:24:42 +03:00
self.putPosition = Position.zero
2016-06-19 11:07:03 +03:00
let emptyCellAttrs = CellAttributes(fontTrait: .None,
foreground: self.foreground, background: self.background, special: self.special)
let emptyRow = Array(count: size.width, repeatedValue: Cell(string: " ", attrs: emptyCellAttrs))
self.cells = Array(count: size.height, repeatedValue: emptyRow)
}
func clear() {
self.clearRegion(self.region)
}
func eolClear() {
self.clearRegion(
2016-07-03 23:08:14 +03:00
Region(top: self.putPosition.row, bottom: self.putPosition.row,
left: self.putPosition.column, right: self.region.right)
)
}
func setScrollRegion(region: Region) {
self.region = region
}
func scroll(count: Int) {
2016-06-15 21:37:02 +03:00
var start, stop, step : Int
if count > 0 {
start = self.region.top;
stop = self.region.bottom - count + 1;
step = 1;
} else {
start = self.region.bottom;
stop = self.region.top - count - 1;
step = -1;
}
// copy cell data
let rangeWithinRow = self.region.left...self.region.right
for i in start.stride(to: stop, by: step) {
self.cells[i].replaceRange(rangeWithinRow, with: self.cells[i + count][rangeWithinRow])
}
// clear cells in the emptied region,
var clearTop, clearBottom: Int
if count > 0 {
clearTop = stop
clearBottom = stop + count - 1
} else {
clearBottom = stop
clearTop = stop + count + 1
}
self.clearRegion(Region(top: clearTop, bottom: clearBottom, left: self.region.left, right: self.region.right))
}
func goto(position: Position) {
2016-07-01 22:24:42 +03:00
self.putPosition = position
}
func moveCursor(position: Position) {
self.screenCursor = position
}
func put(string: String) {
// FIXME: handle the following situation:
// |abcde | <- type
// =>
// |abcde>| <- ">" at the end of the line is wrong -> the XPC could tell the main app whether the string occupies
// | | two cells using vim_strwidth()
2016-07-01 22:24:42 +03:00
self.cells[self.putPosition.row][self.putPosition.column] = Cell(string: string, attrs: self.attrs)
// Increment the column of the put position because neovim calls sets the position only once when drawing
// consecutive cells in the same line
self.putPosition.column += 1
}
func putMarkedText(string: String) {
// NOTE: Maybe there's a better way to indicate marked text than inverting...
2016-07-01 22:24:42 +03:00
self.cells[self.putPosition.row][self.putPosition.column] = Cell(string: string, attrs: self.attrs, marked: true)
self.putPosition.column += 1
}
func unmarkCell(position: Position) {
// NSLog("\(#function): \(position)")
self.cells[position.row][position.column].marked = false
}
func singleIndexFrom(position: Position) -> Int {
return position.row * self.size.width + position.column
}
func positionFromSingleIndex(idx: Int) -> Position {
let row = Int(floor(Double(idx) / Double(self.size.width)))
let column = idx - row * self.size.width
return Position(row: row, column: column)
}
func isCellEmpty(position: Position) -> Bool {
2016-07-05 20:00:27 +03:00
guard self.isSane(position: position) else {
return false
}
if self.cells[position.row][position.column].string.characters.count == 0 {
2016-06-29 21:06:31 +03:00
return true
}
return false
}
func isPreviousCellEmpty(position: Position) -> Bool {
2016-07-01 22:24:42 +03:00
return self.isCellEmpty(self.previousCellPosition(position))
}
func isNextCellEmpty(position: Position) -> Bool {
2016-07-01 22:24:42 +03:00
return self.isCellEmpty(self.nextCellPosition(position))
}
2016-07-01 22:24:42 +03:00
func previousCellPosition(position: Position) -> Position {
return Position(row: position.row, column: max(position.column - 1, 0))
}
2016-06-29 21:06:31 +03:00
func nextCellPosition(position: Position) -> Position {
return Position(row: position.row, column: min(position.column + 1, self.size.width - 1))
}
func cellForSingleIndex(idx: Int) -> Cell {
let position = self.positionFromSingleIndex(idx)
return self.cells[position.row][position.column]
}
2016-06-29 21:06:31 +03:00
private func clearRegion(region: Region) {
2016-06-19 14:39:20 +03:00
// FIXME: sometimes clearRegion gets called without first resizing the Grid. Should we handle this?
guard self.hasData else {
return
}
let clearedAttrs = CellAttributes(fontTrait: .None,
foreground: self.foreground, background: self.background, special: self.special)
let clearedCell = Cell(string: " ", attrs: clearedAttrs)
let clearedRow = Array(count: region.right - region.left + 1, repeatedValue: clearedCell)
for i in region.top...region.bottom {
self.cells[i].replaceRange(region.left...region.right, with: clearedRow)
}
}
2016-07-05 20:00:27 +03:00
private func isSane(position position: Position) -> Bool {
guard position.row < self.size.height && position.column < self.size.width else {
return false
}
return true
}
2016-06-19 11:07:03 +03:00
}