From c95d2c32014d070da6a9a61fee5243dc22f284ff Mon Sep 17 00:00:00 2001 From: Tae Won Ha Date: Sat, 18 Jun 2016 11:43:37 +0200 Subject: [PATCH] Draw bold/italic/underline and correct color --- NeoVimXpc/NeoVimXpcImpl.m | 39 +++++++-- SwiftNeoVim/ColorUtils.swift | 8 +- SwiftNeoVim/Grid.swift | 30 ++++--- SwiftNeoVim/NeoVim.swift | 26 ------ SwiftNeoVim/NeoVimUiBridgeProtocol.h | 24 ++++-- SwiftNeoVim/NeoVimView.swift | 115 +++++++++++++++++++------- SwiftNeoVim/NeoVimViewDelegate.swift | 1 + SwiftNeoVim/TextDrawer.h | 16 ++-- SwiftNeoVim/TextDrawer.m | 119 ++++++++++++++++++++++++--- nvox/AppDelegate.swift | 4 + 10 files changed, 273 insertions(+), 109 deletions(-) diff --git a/NeoVimXpc/NeoVimXpcImpl.m b/NeoVimXpc/NeoVimXpcImpl.m index f8ffed14..a7744c10 100644 --- a/NeoVimXpc/NeoVimXpcImpl.m +++ b/NeoVimXpc/NeoVimXpcImpl.m @@ -41,15 +41,15 @@ typedef struct { // FIXME: dunno whether we need this: copied from tui.c bool cont_received; SignalWatcher cont_handle; -} OsxXpcUiData; +} XpcUiData; static void sigcont_cb(SignalWatcher *watcher __unused, int signum __unused, void *data) { - ((OsxXpcUiData *) data)->cont_received = true; + ((XpcUiData *) data)->cont_received = true; } static void osx_xpc_ui_scheduler(Event event, void *d) { UI *ui = d; - OsxXpcUiData *data = ui->data; + XpcUiData *data = ui->data; loop_schedule(data->loop, event); } @@ -57,7 +57,7 @@ static void osx_xpc_ui_main(UIBridgeData *bridge, UI *ui) { Loop loop; loop_init(&loop, NULL); - OsxXpcUiData *data = xcalloc(1, sizeof(OsxXpcUiData)); + XpcUiData *data = xcalloc(1, sizeof(XpcUiData)); ui->data = data; data->bridge = bridge; data->loop = &loop; @@ -91,7 +91,7 @@ static void osx_xpc_ui_main(UIBridgeData *bridge, UI *ui) { // FIXME: dunno whether we need this: copied from tui.c static void suspend_event(void **argv) { UI *ui = argv[0]; - OsxXpcUiData *data = ui->data; + XpcUiData *data = ui->data; data->cont_received = false; kill(0, SIGTSTP); @@ -166,7 +166,30 @@ static void xpc_ui_scroll(UI *ui __unused, int count) { static void xpc_ui_highlight_set(UI *ui __unused, HlAttrs attrs) { //printf("highlight set\n"); - [neo_vim_osx_ui highlightSet:*((HighlightAttributes *)(&attrs))]; + FontTrait trait = FontTraitNone; + if (attrs.italic) { + trait |= FontTraitItalic; + } + if (attrs.bold) { + trait |= FontTraitBold; + } + if (attrs.underline) { + trait |= FontTraitUnderline; + } + if (attrs.undercurl) { + trait |= FontTraitUndercurl; + } + CellAttributes cellAttrs; + cellAttrs.fontTrait = trait; + + unsigned int fg = attrs.foreground == -1 ? qDefaultForeground : *((unsigned int*)(&attrs.foreground)); + unsigned int bg = attrs.background == -1 ? qDefaultBackground : *((unsigned int*)(&attrs.background)); + + cellAttrs.foreground = attrs.reverse ? bg : fg; + cellAttrs.background = attrs.reverse ? fg : bg; + cellAttrs.special = attrs.special == -1 ? qDefaultSpecial : *((unsigned int*)(&attrs.special)); + + [neo_vim_osx_ui highlightSet:cellAttrs]; } static void xpc_ui_put(UI *ui __unused, uint8_t *str, size_t len) { @@ -210,7 +233,7 @@ static void xpc_ui_suspend(UI *ui __unused) { //printf("suspend\n"); [neo_vim_osx_ui suspend]; - OsxXpcUiData *data = ui->data; + XpcUiData *data = ui->data; // FIXME: dunno whether we need this: copied from tui.c // kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT // before continuing. This is done in another callback to avoid @@ -236,7 +259,7 @@ static void xpc_ui_stop(UI *ui __unused) { //printf("stop\n"); [neo_vim_osx_ui stop]; - OsxXpcUiData *data = (OsxXpcUiData *) ui->data; + XpcUiData *data = (XpcUiData *) ui->data; data->stop = true; } diff --git a/SwiftNeoVim/ColorUtils.swift b/SwiftNeoVim/ColorUtils.swift index 7fbfcf75..06925fea 100644 --- a/SwiftNeoVim/ColorUtils.swift +++ b/SwiftNeoVim/ColorUtils.swift @@ -7,10 +7,10 @@ import Cocoa class ColorUtils { - static func colorFromCode(rgb: UInt32) -> NSColor { - let red = (CGFloat((rgb >> 16) & 0x000000FF)) / 255.0; - let green = (CGFloat((rgb >> 8 ) & 0x000000FF)) / 255.0; - let blue = (CGFloat(rgb & 0x000000FF)) / 255.0; + static func colorFromCodeIgnoringAlpha(rgb: UInt32) -> NSColor { + let red = (CGFloat((rgb >> 16) & 0xFF)) / 255.0; + let green = (CGFloat((rgb >> 8) & 0xFF)) / 255.0; + let blue = (CGFloat((rgb ) & 0xFF)) / 255.0; return NSColor(SRGBRed: red, green: green, blue: blue, alpha: 1.0) } diff --git a/SwiftNeoVim/Grid.swift b/SwiftNeoVim/Grid.swift index 854fce39..efdae7d2 100644 --- a/SwiftNeoVim/Grid.swift +++ b/SwiftNeoVim/Grid.swift @@ -7,7 +7,7 @@ import Foundation struct Cell: CustomStringConvertible { let string: String - let attrs: HighlightAttributes + let attrs: CellAttributes var description: String { return self.string.characters.count > 0 ? self.string : "*" @@ -63,23 +63,23 @@ struct Region: CustomStringConvertible { /// Almost a verbatim copy of ugrid.c of NeoVim class Grid: CustomStringConvertible { - private let qEmptyHighlightAttributes = HighlightAttributes( - bold: false, underline: false, undercurl: false, italic: false, - reverse: false, foreground: -1, background: -1, special: -1 + private let qEmptyCellAttributes = CellAttributes( + fontTrait: .None, + foreground: qDefaultForeground, background: qDefaultBackground, special: qDefaultSpecial ) // not static due to https://bugs.swift.org/browse/SR-1739 private(set) var region = Region.zero private(set) var size = Size.zero private(set) var position = Position.zero - var foreground: Int32 = -1 - var background: Int32 = -1 - var special: Int32 = -1 + var foreground = qDefaultForeground + var background = qDefaultBackground + var special = qDefaultSpecial - var attrs: HighlightAttributes = HighlightAttributes( - bold: false, underline: false, undercurl: false, italic: false, - reverse: false, foreground: -1, background: -1, special: -1 - ) // not using qEmptyHighlightAttributes because not static due to https://bugs.swift.org/browse/SR-1739 + var attrs: CellAttributes = CellAttributes( + fontTrait: .None, + foreground: qDefaultForeground, background: qDefaultBackground, special: qDefaultSpecial + ) // not using qEmptyCellAttributes because not static due to https://bugs.swift.org/browse/SR-1739 private(set) var cells: [[Cell]] = [] @@ -96,7 +96,7 @@ class Grid: CustomStringConvertible { self.size = size self.position = Position.zero - let emptyRow = Array(count: size.width, repeatedValue: Cell(string: " ", attrs: qEmptyHighlightAttributes)) + let emptyRow = Array(count: size.width, repeatedValue: Cell(string: " ", attrs: qEmptyCellAttributes)) self.cells = Array(count: size.height, repeatedValue: emptyRow) } @@ -158,10 +158,8 @@ class Grid: CustomStringConvertible { return } - let clearedAttrs = HighlightAttributes( - bold: false, underline: false, undercurl: false, italic: false, - reverse: false, foreground: self.foreground, background: self.background, special: self.background - ) + 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) diff --git a/SwiftNeoVim/NeoVim.swift b/SwiftNeoVim/NeoVim.swift index 7ab4bc99..5dda8f5a 100644 --- a/SwiftNeoVim/NeoVim.swift +++ b/SwiftNeoVim/NeoVim.swift @@ -6,32 +6,6 @@ import Foundation public class NeoVim { - - enum UiEvent { - case MoveCursor(position: Position) - case Put(string: String) - case Resize(size: Size) - case SetHighlightAttributes(attrs: HighlightAttributes) - case Clear - case EolClear - case Flush - } - - struct Size { - let width: Int - let height: Int - } - - struct Position { - var row: Int - var column: Int - } - - enum ColorKind { - case Foreground - case Background - case Special - } private static let qXpcName = "com.qvacua.nvox.xpc" diff --git a/SwiftNeoVim/NeoVimUiBridgeProtocol.h b/SwiftNeoVim/NeoVimUiBridgeProtocol.h index f2096750..6ff24831 100644 --- a/SwiftNeoVim/NeoVimUiBridgeProtocol.h +++ b/SwiftNeoVim/NeoVimUiBridgeProtocol.h @@ -5,11 +5,25 @@ #import -// TODO: keep in sync with HlAttrs struct in ui.h +typedef NS_ENUM(NSUInteger, FontTrait) { + FontTraitNone = 0, + FontTraitItalic = (1 << 0), + FontTraitBold = (1 << 1), + FontTraitUnderline = (1 << 2), + FontTraitUndercurl = (1 << 3) +}; + typedef struct { - bool bold, underline, undercurl, italic, reverse; - int foreground, background, special; -} HighlightAttributes; + FontTrait fontTrait; + + unsigned int foreground; + unsigned int background; + unsigned int special; +} CellAttributes; + +#define qDefaultForeground 0xFF000000 +#define qDefaultBackground 0xFFFFFFFF +#define qDefaultSpecial 0xFFFF0000 @protocol NeoVimUiBridgeProtocol @@ -50,7 +64,7 @@ typedef struct { - (void)setScrollRegionToTop:(int)top bottom:(int)bottom left:(int)left right:(int)right; - (void)scroll:(int)count; -- (void)highlightSet:(HighlightAttributes)attrs; +- (void)highlightSet:(CellAttributes)attrs; /** * Draw string at the current cursor which was set by a previous cursorGotoRow:column callback. diff --git a/SwiftNeoVim/NeoVimView.swift b/SwiftNeoVim/NeoVimView.swift index f194fcfe..19f98f3d 100644 --- a/SwiftNeoVim/NeoVimView.swift +++ b/SwiftNeoVim/NeoVimView.swift @@ -4,7 +4,20 @@ */ import Cocoa -import RxSwift + +func == (left: CellAttributes, right: CellAttributes) -> Bool { + if left.foreground != right.foreground { return false } + if left.fontTrait != right.fontTrait { return false } + + if left.background != right.background { return false } + if left.special != right.special { return false } + + return true +} + +func != (left: CellAttributes, right: CellAttributes) -> Bool { + return !(left == right) +} private struct RowFragment: CustomStringConvertible { @@ -16,35 +29,52 @@ private struct RowFragment: CustomStringConvertible { } } +private struct AttributedRowFragment: CustomStringConvertible { + + let row: Int + let range: Range + let attrs: CellAttributes + + var description: String { + return "AttributedRowFragment<\(row): \(range)\n\(attrs)>" + } +} + 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 var font: NSFont { + didSet { + self.drawer.font = self.font + self.cellSize = self.drawer.cellSize + self.lineSpace = self.drawer.lineSpace + + // FIXME: resize and redraw + } + } private let xpc: NeoVimXpc - private let drawer = TextDrawer() + private let drawer: TextDrawer - private var cellSize: CGSize = CGSizeMake(0, 0) + private var cellSize = CGSize.zero + private var lineSpace = CGFloat(0) private let grid = Grid() init(frame rect: NSRect = CGRect.zero, xpc: NeoVimXpc) { self.xpc = xpc + + self.font = NSFont(name: "Menlo", size: 13)! + self.drawer = TextDrawer(font: font) + 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) + self.cellSize = self.drawer.cellSize + self.lineSpace = self.drawer.lineSpace } override public func keyDown(theEvent: NSEvent) { @@ -56,36 +86,32 @@ public class NeoVimView: NSView { 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 + + self.attributedRowFragmentsIntersecting(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() + self.drawBackground(positions: positions, background: rowFrag.attrs.background) + 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 - ) + let glyphPositions = positions.map { CGPoint(x: $0.x, y: $0.y + self.lineSpace) } + self.drawer.drawString(string, + positions: UnsafeMutablePointer(glyphPositions), positionsCount: positions.count, + highlightAttrs: rowFrag.attrs, + context: context) } -// Swift.print("------- DRAW END") } - private func drawBackground(positions positions: [CGPoint]) { - ColorUtils.colorFromCode(self.backgroundColor).set() + private func drawBackground(positions positions: [CGPoint], background: UInt32) { + ColorUtils.colorFromCodeIgnoringAlpha(background).set() let backgroundRect = CGRect( x: positions[0].x, y: positions[0].y, width: positions.last!.x + self.cellSize.width, height: self.cellSize.height @@ -93,6 +119,31 @@ public class NeoVimView: NSView { backgroundRect.fill() } + private func attributedRowFragmentsIntersecting(rects rects: [CGRect]) -> [AttributedRowFragment] { + return self.rowFragmentsIntersecting(rects: rects) + .map { rowFrag -> [AttributedRowFragment] in + let row = rowFrag.row + let rowCells = self.grid.cells[rowFrag.row] + let range = rowFrag.range + let startIndex = range.startIndex + + var result = [ + AttributedRowFragment(row: row, range: startIndex...startIndex, attrs: rowCells[startIndex].attrs) + ] + range.forEach { idx in + if rowCells[idx].attrs == result.last!.attrs { + let last = result.popLast()! + result.append(AttributedRowFragment(row: row, range: last.range.startIndex...idx, attrs: last.attrs)) + } else { + result.append(AttributedRowFragment(row: row, range: idx...idx, attrs: rowCells[idx].attrs)) + } + } + + return result + } + .flatMap { $0 } + } + private func rowFragmentsIntersecting(rects rects: [CGRect]) -> [RowFragment] { return rects .map { rect -> Region in @@ -222,7 +273,7 @@ extension NeoVimView: NeoVimUiBridgeProtocol { } } - public func highlightSet(attrs: HighlightAttributes) { + public func highlightSet(attrs: CellAttributes) { gui { // Swift.print("### set highlight") self.grid.attrs = attrs @@ -274,7 +325,7 @@ extension NeoVimView: NeoVimUiBridgeProtocol { } public func setTitle(title: String) { - // Swift.print("### set title: \(title)") + self.delegate?.setTitle(title) } public func setIcon(icon: String) { diff --git a/SwiftNeoVim/NeoVimViewDelegate.swift b/SwiftNeoVim/NeoVimViewDelegate.swift index 42b35614..a69f8e31 100644 --- a/SwiftNeoVim/NeoVimViewDelegate.swift +++ b/SwiftNeoVim/NeoVimViewDelegate.swift @@ -7,5 +7,6 @@ import Cocoa public protocol NeoVimViewDelegate { + func setTitle(title: String) func resizeToSize(size: CGSize) } diff --git a/SwiftNeoVim/TextDrawer.h b/SwiftNeoVim/TextDrawer.h index fbaa89a4..070d6eb3 100644 --- a/SwiftNeoVim/TextDrawer.h +++ b/SwiftNeoVim/TextDrawer.h @@ -6,13 +6,19 @@ @import Cocoa; @import CoreText; +#import "NeoVimUiBridgeProtocol.h" + @interface TextDrawer : NSObject -- (void)drawString:(NSString *_Nonnull)theString - positions:(CGPoint *_Nonnull)positions - font:(NSFont *_Nonnull)font - foreground:(unsigned int)foreground - background:(unsigned int)background +@property (nonatomic, nonnull, retain) NSFont *font; +@property (nonatomic, readonly) CGFloat lineSpace; +@property (nonatomic, readonly) CGSize cellSize; + +- (instancetype _Nonnull)initWithFont:(NSFont *_Nonnull)font; + +- (void)drawString:(NSString *_Nonnull)string + positions:(CGPoint *_Nonnull)positions positionsCount:(NSInteger)positionsCount + highlightAttrs:(CellAttributes)attrs context:(CGContextRef _Nonnull)context; @end diff --git a/SwiftNeoVim/TextDrawer.m b/SwiftNeoVim/TextDrawer.m index 824bd983..e83a499c 100644 --- a/SwiftNeoVim/TextDrawer.m +++ b/SwiftNeoVim/TextDrawer.m @@ -8,6 +8,7 @@ #import "TextDrawer.h" #import "MMCoreTextView.h" +#import "NeoVimUiBridgeProtocol.h" #define ALPHA(color_code) (((color_code >> 24) & 0xff) / 255.0f) #define RED(color_code) (((color_code >> 16) & 0xff) / 255.0f) @@ -15,41 +16,95 @@ #define BLUE(color_code) (((color_code ) & 0xff) / 255.0f) @implementation TextDrawer { - NSMutableArray *fontCache; + NSLayoutManager *_layoutManager; + + NSFont *_font; + CGFloat _fontDescent; + CGFloat _lineGap; + + NSMutableArray *_fontLookupCache; + NSMutableDictionary *_fontTraitCache; } -- (instancetype)init { +- (void)setFont:(NSFont *)font { + [_font autorelease]; + _font = [font retain]; + + _cellSize = CGSizeMake( + round([@"m" sizeWithAttributes:@{ NSFontAttributeName : _font }].width), + [_layoutManager defaultLineHeightForFont:_font] + _lineSpace + ); + // https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/FontHandling/FontHandling.html + _lineGap = _cellSize.height - _font.ascender - _font.descender; + _fontDescent = CTFontGetDescent((CTFontRef) _font); +} + +- (instancetype)initWithFont:(NSFont *_Nonnull)font { self = [super init]; if (self == nil) { return nil; } - fontCache = [[NSMutableArray alloc] initWithCapacity:4]; + _layoutManager = [[NSLayoutManager alloc] init]; + _fontLookupCache = [[NSMutableArray alloc] init]; + _fontTraitCache = [[NSMutableDictionary alloc] init]; + + _lineSpace = 4; + self.font = font; return self; } - (void)dealloc { - [fontCache release]; + [_layoutManager release]; + [_font release]; + [_fontLookupCache release]; + [_fontTraitCache release]; [super dealloc]; } /** - * We assume that the caller has already called + * We assume that the background is drawn elsewhere and that the caller has already called * * CGContextSetTextMatrix(context, CGAffineTransformIdentity); // or some other matrix * CGContextSetTextDrawingMode(context, kCGTextFill); // or some other mode */ -- (void)drawString:(NSString *_Nonnull)theString +- (void)drawString:(NSString *_Nonnull)string positions:(CGPoint *_Nonnull)positions - font:(NSFont *_Nonnull)theFont - foreground:(unsigned int)foreground - background:(unsigned int)background + positionsCount:(NSInteger)positionsCount + highlightAttrs:(CellAttributes)attrs context:(CGContextRef _Nonnull)context { - CFStringRef string = (CFStringRef) theString; - CTFontRef font = (CTFontRef) theFont; + CGContextSaveGState(context); + + if (attrs.fontTrait & FontTraitUnderline) { + CGRect rect = { + {positions[0].x, positions[0].y - 1}, + {positions[0].x + positions[positionsCount - 1].x + _cellSize.width, 1} + }; + [self drawUnderline:rect color:attrs.special context:context]; + } + + [self drawString:string positions:positions + fontTrait:attrs.fontTrait foreground:attrs.foreground + context:context]; + + CGContextRestoreGState(context); +} + +- (void)drawUnderline:(CGRect)rect color:(unsigned int)color context:(CGContextRef _Nonnull)context { + CGContextSetRGBFillColor(context, RED(color), GREEN(color), BLUE(color), ALPHA(color)); + CGContextFillRect(context, rect); +} + +- (void)drawString:(NSString *_Nonnull)nsstring + positions:(CGPoint *_Nonnull)positions + fontTrait:(FontTrait)fontTrait + foreground:(unsigned int)foreground + context:(CGContextRef _Nonnull)context +{ + CFStringRef string = (CFStringRef) nsstring; UniChar *unibuffer = NULL; UniCharCount unilength = (UniCharCount) CFStringGetLength(string); @@ -61,14 +116,52 @@ } CGGlyph *glyphs = malloc(unilength * sizeof(UniChar)); + CTFontRef fontWithTraits = [self fontWithTrait:fontTrait]; - recurseDraw(unichars, glyphs, positions, unilength, context, font, fontCache, YES); + CGContextSetRGBFillColor(context, RED(foreground), GREEN(foreground), BLUE(foreground), 1.0); + recurseDraw(unichars, glyphs, positions, unilength, context, fontWithTraits, _fontLookupCache, YES); + CFRelease(fontWithTraits); + free(glyphs); if (unibuffer != NULL) { free(unibuffer); } +} - free(glyphs); +/** + * The caller _must_ CFRelease the returned CTFont! + */ +- (CTFontRef)fontWithTrait:(FontTrait)fontTrait { + if (fontTrait == FontTraitNone) { + return CFRetain(_font); + } + + CTFontSymbolicTraits traits = (CTFontSymbolicTraits) 0; + if (fontTrait & FontTraitBold) { + traits |= kCTFontBoldTrait; + } + + if (fontTrait & FontTraitItalic) { + traits |= kCTFontItalicTrait; + } + + NSFont *cachedFont = _fontTraitCache[@(traits)]; + if (cachedFont != nil) { + return CFRetain(cachedFont); + } + + if (traits == 0) { + return CFRetain(_font); + } + + CTFontRef fontWithTraits = CTFontCreateCopyWithSymbolicTraits((CTFontRef) _font, 0.0, NULL, traits, traits); + if (fontWithTraits == NULL) { + return CFRetain(_font); + } + + _fontTraitCache[@(traits)] = (NSFont *) fontWithTraits; + + return fontWithTraits; } @end diff --git a/nvox/AppDelegate.swift b/nvox/AppDelegate.swift index 85ca32af..89953d94 100644 --- a/nvox/AppDelegate.swift +++ b/nvox/AppDelegate.swift @@ -40,4 +40,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NeoVimViewDelegate { func resizeToSize(size: CGSize) { self.neoVim.view.setFrameSize(size) } + + func setTitle(title: String) { + self.window.title = title + } }