1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-25 14:52:19 +03:00

Merge branch 'issue/339-md-preview' into develop

This commit is contained in:
Tae Won Ha 2017-01-08 16:07:36 +01:00
commit aba4fbec2f
No known key found for this signature in database
GPG Key ID: E40743465B5B8B44
46 changed files with 2380 additions and 172 deletions

View File

@ -3,3 +3,5 @@ github "PureLayout/PureLayout" == 3.0.2
github "eonil/FileSystemEvents" "master"
github "sparkle-project/Sparkle" == 1.15.1
github "qvacua/CocoaFontAwesome" "master"
github "qvacua/CocoaMarkdown" "master"
github "sindresorhus/github-markdown-css" == 2.4.1

View File

@ -1,6 +1,8 @@
github "qvacua/CocoaFontAwesome" "71865fc2c0275ebc5c8601edc23290e85aca9979"
github "qvacua/CocoaMarkdown" "5d1c1e3dd74486dfc358c9cc3ddd7e993842113d"
github "eonil/FileSystemEvents" "aa5c6af1fd35939f9aca3b9eba3b672bfa549b3a"
github "Quick/Nimble" "v5.1.0"
github "PureLayout/PureLayout" "v3.0.2"
github "ReactiveX/RxSwift" "3.1.0"
github "sparkle-project/Sparkle" "1.15.1"
github "sindresorhus/github-markdown-css" "v2.4.1"

View File

@ -36,8 +36,7 @@ typedef NS_ENUM(NSUInteger, NeoVimServerMsgId) {
NeoVimServerMsgIdStop,
NeoVimServerMsgIdDirtyStatusChanged,
NeoVimServerMsgIdCwdChanged,
NeoVimServerMsgIdBufferEvent,
NeoVimServerMsgIdAutoCommandEvent,
#ifdef DEBUG
NeoVimServerDebug1,
@ -53,6 +52,7 @@ typedef NS_ENUM(NSUInteger, NeoVimAgentMsgId) {
NeoVimAgentMsgIdDelete,
NeoVimAgentMsgIdResize,
NeoVimAgentMsgIdSelectWindow,
NeoVimAgentMsgIdCursorGoto,
NeoVimAgentMsgIdQuit,
NeoVimAgentMsgIdGetDirtyDocs,

View File

@ -99,6 +99,8 @@ static CFDataRef local_server_callback(CFMessagePortRef local, SInt32 msgid, CFD
case NeoVimAgentMsgIdDelete: return null_data_async(data, neovim_delete);
case NeoVimAgentMsgIdCursorGoto: return null_data_async(data, neovim_cursor_goto);
default: return NULL;
}

View File

@ -27,3 +27,5 @@ extern void neovim_vim_command(void **argv);
extern void neovim_vim_input(void **argv);
extern void neovim_vim_input_marked_text(void **argv);
extern void neovim_delete(void **argv);
extern void neovim_cursor_goto(void **argv);

View File

@ -24,12 +24,10 @@
#import <nvim/api/vim.h>
#import <nvim/ui.h>
#import <nvim/ui_bridge.h>
#import <nvim/main.h>
#import <nvim/ex_docmd.h>
#import <nvim/ex_getln.h>
#import <nvim/fileio.h>
#import <nvim/undo.h>
#import <nvim/eval.h>
#import <nvim/api/window.h>
#define pun_type(t, x) (*((t *) (&(x))))
@ -100,20 +98,6 @@ static bool has_dirty_docs() {
return false;
}
static void send_dirty_status() {
bool new_dirty_status = has_dirty_docs();
DLOG("dirty status: %d vs. %d", _dirty, new_dirty_status);
if (_dirty == new_dirty_status) {
return;
}
_dirty = new_dirty_status;
DLOG("sending dirty status: %d", _dirty);
NSData *data = [[NSData alloc] initWithBytes:&_dirty length:sizeof(bool)];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdDirtyStatusChanged data:data];
[data release];
}
static void insert_marked_text(NSString *markedText) {
_marked_text = [markedText retain]; // release when the final text is input in -vimInput
@ -183,6 +167,20 @@ static void server_ui_main(UIBridgeData *bridge, UI *ui) {
xfree(ui);
}
static void send_dirty_status() {
bool new_dirty_status = has_dirty_docs();
DLOG("dirty status: %d vs. %d", _dirty, new_dirty_status);
if (_dirty == new_dirty_status) {
return;
}
_dirty = new_dirty_status;
DLOG("sending dirty status: %d", _dirty);
NSData *data = [[NSData alloc] initWithBytes:&_dirty length:sizeof(bool)];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdDirtyStatusChanged data:data];
[data release];
}
#pragma mark NeoVim's UI callbacks
static void server_ui_resize(UI *ui __unused, int width, int height) {
@ -204,9 +202,15 @@ static void server_ui_cursor_goto(UI *ui __unused, int row, int col) {
_put_row = row;
_put_column = col;
int values[] = {row, col, screen_cursor_row(), screen_cursor_column()};
DLOG("%d:%d - %d:%d", values[0], values[1], values[2], values[3]);
NSData *data = [[NSData alloc] initWithBytes:values length:(4 * sizeof(int))];
int values[] = {
row, col,
screen_cursor_row(), screen_cursor_column(),
(int) curwin->w_cursor.lnum, curwin->w_cursor.col + 1
};
DLOG("%d:%d - %d:%d - %d:%d", values[0], values[1], values[2], values[3], values[4], values[5]);
NSData *data = [[NSData alloc] initWithBytes:values length:(6 * sizeof(int))];
[_neovim_server sendMessageWithId:NeoVimServerMsgIdSetPosition data:data];
[data release];
}
@ -442,31 +446,39 @@ void custom_ui_start(void) {
void custom_ui_autocmds_groups(
event_T event, char_u *fname, char_u *fname_io, int group, bool force, buf_T *buf, exarg_T *eap
) {
// We don't need these events in the UI (yet) and they slow down scrolling: Enable them, if necessary, only after
// optimizing the scrolling.
if (event == EVENT_CURSORMOVED || event == EVENT_CURSORMOVEDI) {
return;
}
@autoreleasepool {
DLOG("got event %d for file %s in group %d.", event, fname, group);
switch (event) {
// Dirty status: Did we get them all?
case EVENT_TEXTCHANGED:
case EVENT_TEXTCHANGEDI:
case EVENT_BUFWRITEPOST:
case EVENT_BUFLEAVE:
if (event == EVENT_TEXTCHANGED
|| event == EVENT_TEXTCHANGEDI
|| event == EVENT_BUFWRITEPOST
|| event == EVENT_BUFLEAVE)
{
send_dirty_status();
return;
// For buffer list changes
case EVENT_BUFWINENTER:
case EVENT_BUFWINLEAVE:
[_neovim_server sendMessageWithId:NeoVimServerMsgIdBufferEvent];
break;
case EVENT_CWDCHANGED:
[_neovim_server sendMessageWithId:NeoVimServerMsgIdCwdChanged];
break;
default:
break;
}
NSUInteger eventCode = event;
NSMutableData *data;
if (buf == NULL) {
data = [[NSMutableData alloc] initWithBytes:&eventCode length:sizeof(NSUInteger)];
} else {
NSInteger bufHandle = buf->handle;
data = [[NSMutableData alloc] initWithCapacity:(sizeof(NSUInteger) + sizeof(NSInteger))];
[data appendBytes:&eventCode length:sizeof(NSUInteger)];
[data appendBytes:&bufHandle length:sizeof(NSInteger)];
}
[_neovim_server sendMessageWithId:NeoVimServerMsgIdAutoCommandEvent data:data];
[data release];
}
}
@ -650,18 +662,8 @@ void neovim_vim_command_output(void **argv) {
NSString *input = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
Error err = ERROR_INIT;
// We don't know why nvim_command_output does not work when the optimization level is set to -Os.
// If set to -O0, nvim_command_output works fine... -_-
// String commandOutput = nvim_command_output((String) {
// .data = (char *) input.cstr,
// .size = [input lengthOfBytesUsingEncoding:NSUTF8StringEncoding]
// }, &err);
do_cmdline_cmd("redir => v:command_output");
nvim_command(vim_string_from(input), &err);
do_cmdline_cmd("redir END");
char_u *output = get_vim_var_str(VV_COMMAND_OUTPUT);
String commandOutput = nvim_command_output(vim_string_from(input), &err);
char_u *output = (char_u *) commandOutput.data;
// FIXME: handle err.set == true
NSString *result = nil;
@ -773,7 +775,9 @@ void neovim_vim_command(void **argv) {
Error err = ERROR_INIT;
nvim_command(vim_string_from(input), &err);
// FIXME: handle err.set == true
if (err.set) {
WLOG("ERROR while executing command %s: %s", input.cstr, err.msg);
}
[input release];
@ -867,3 +871,36 @@ void neovim_delete(void **argv) {
return nil;
});
}
void neovim_cursor_goto(void **argv) {
work_async(argv, ^NSData *(NSData *data) {
const int *values = data.bytes;
Array position = ARRAY_DICT_INIT;
position.size = 2;
position.capacity = 2;
position.items = xmalloc(2 * sizeof(Object));
Object row = OBJECT_INIT;
row.type = kObjectTypeInteger;
row.data.integer = values[0];
Object col = OBJECT_INIT;
col.type = kObjectTypeInteger;
col.data.integer = values[1];
position.items[0] = row;
position.items[1] = col;
Error err = ERROR_INIT;
nvim_win_set_cursor(nvim_get_current_win(), position, &err);
// The above call seems to be not enough...
nvim_input((String) { .data="<ESC>", .size=5 });
xfree(position.items);
return nil;
});
}

View File

@ -21,6 +21,9 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, weak) id <NeoVimUiBridgeProtocol> bridge;
- (instancetype)initWithUuid:(NSString *)uuid;
- (void)debug;
- (void)quit;
- (bool)runLocalServerAndNeoVim;
@ -32,6 +35,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)deleteCharacters:(NSInteger)count;
- (void)resizeToWidth:(int)width height:(int)height;
- (void)cursorGoToRow:(int)row column:(int)column;
- (bool)hasDirtyDocs;
- (NSString *)escapedFileName:(NSString *)fileName;

View File

@ -23,6 +23,7 @@ static type *data_to_ ## type ## _array(NSData *data, NSUInteger count) { \
}
data_to_array(int)
data_to_array(NSUInteger)
data_to_array(bool)
data_to_array(CellAttributes)
@ -83,7 +84,7 @@ static CFDataRef local_server_callback(CFMessagePortRef local, SInt32 msgid, CFD
NSCondition *_neoVimReadyCondition;
bool _isInitErrorPresent;
uint32_t _neoVimIsQuitting;
volatile uint32_t _neoVimIsQuitting;
}
- (instancetype)initWithUuid:(NSString *)uuid {
@ -107,6 +108,12 @@ static CFDataRef local_server_callback(CFMessagePortRef local, SInt32 msgid, CFD
return _neoVimIsQuitting == 1;
}
- (void)debug {
#ifdef DEBUG
[self sendMessageWithId:NeoVimAgentDebug1 data:nil expectsReply:NO];
#endif
}
// We cannot use -dealloc for this since -dealloc is not called until the run loop in the thread stops.
- (void)quit {
OSAtomicOr32Barrier(1, &_neoVimIsQuitting);
@ -231,11 +238,17 @@ static CFDataRef local_server_callback(CFMessagePortRef local, SInt32 msgid, CFD
[self sendMessageWithId:NeoVimAgentMsgIdResize data:data expectsReply:NO];
}
- (void)cursorGoToRow:(int)row column:(int)column {
int values[] = { row, column };
NSData *data = [[NSData alloc] initWithBytes:values length:(2 * sizeof(int))];
[self sendMessageWithId:NeoVimAgentMsgIdCursorGoto data:data expectsReply:NO];
}
- (bool)hasDirtyDocs {
NSData *response = [self sendMessageWithId:NeoVimAgentMsgIdGetDirtyDocs data:nil expectsReply:YES];
if (response == nil) {
log4Warn("The response for the msg %lu was nil.", NeoVimAgentMsgIdGetDirtyDocs);
return YES;
return NO;
}
NSNumber *value = [NSKeyedUnarchiver unarchiveObjectWithData:response];
@ -439,9 +452,10 @@ static CFDataRef local_server_callback(CFMessagePortRef local, SInt32 msgid, CFD
return;
case NeoVimServerMsgIdSetPosition: {
int *values = data_to_int_array(data, 4);
int *values = data_to_int_array(data, 6);
[_bridge gotoPosition:(Position) { .row = values[0], .column = values[1] }
screenCursor:(Position) { .row = values[2], .column = values[3] }];
screenCursor:(Position) { .row = values[2], .column = values[3] }
currentPosition:(Position) { .row = values[4], .column = values[5] }];
return;
}
@ -566,13 +580,18 @@ static CFDataRef local_server_callback(CFMessagePortRef local, SInt32 msgid, CFD
return;
}
case NeoVimServerMsgIdCwdChanged:
[_bridge cwdChanged];
return;
case NeoVimServerMsgIdBufferEvent:
[_bridge bufferListChanged];
case NeoVimServerMsgIdAutoCommandEvent: {
if (data.length == sizeof(NSUInteger) + sizeof(NSInteger)) {
NSUInteger *values = (NSUInteger *) data.bytes;
NeoVimAutoCommandEvent event = (NeoVimAutoCommandEvent) values[0];
NSInteger bufferHandle = ((NSInteger *)(values + 1))[0];
[_bridge autoCommandEvent:event bufferHandle:bufferHandle];
} else {
NSUInteger *values = data_to_NSUInteger_array(data, 1);
[_bridge autoCommandEvent:(NeoVimAutoCommandEvent) values[0] bufferHandle:-1];
}
return;
}
default:
return;

View File

@ -0,0 +1,97 @@
@import Foundation;
typedef NS_ENUM(NSUInteger, NeoVimAutoCommandEvent) {
NeoVimAutoCommandEventBUFADD = 0,
NeoVimAutoCommandEventBUFDELETE = 1,
NeoVimAutoCommandEventBUFENTER = 2,
NeoVimAutoCommandEventBUFFILEPOST = 3,
NeoVimAutoCommandEventBUFFILEPRE = 4,
NeoVimAutoCommandEventBUFHIDDEN = 5,
NeoVimAutoCommandEventBUFLEAVE = 6,
NeoVimAutoCommandEventBUFNEW = 7,
NeoVimAutoCommandEventBUFNEWFILE = 8,
NeoVimAutoCommandEventBUFREADCMD = 9,
NeoVimAutoCommandEventBUFREADPOST = 10,
NeoVimAutoCommandEventBUFREADPRE = 11,
NeoVimAutoCommandEventBUFUNLOAD = 12,
NeoVimAutoCommandEventBUFWINENTER = 13,
NeoVimAutoCommandEventBUFWINLEAVE = 14,
NeoVimAutoCommandEventBUFWIPEOUT = 15,
NeoVimAutoCommandEventBUFWRITECMD = 16,
NeoVimAutoCommandEventBUFWRITEPOST = 17,
NeoVimAutoCommandEventBUFWRITEPRE = 18,
NeoVimAutoCommandEventCMDUNDEFINED = 19,
NeoVimAutoCommandEventCMDWINENTER = 20,
NeoVimAutoCommandEventCMDWINLEAVE = 21,
NeoVimAutoCommandEventCOLORSCHEME = 22,
NeoVimAutoCommandEventCOMPLETEDONE = 23,
NeoVimAutoCommandEventCURSORHOLD = 24,
NeoVimAutoCommandEventCURSORHOLDI = 25,
NeoVimAutoCommandEventCURSORMOVED = 26,
NeoVimAutoCommandEventCURSORMOVEDI = 27,
NeoVimAutoCommandEventCWDCHANGED = 28,
NeoVimAutoCommandEventENCODINGCHANGED = 29,
NeoVimAutoCommandEventFILEAPPENDCMD = 30,
NeoVimAutoCommandEventFILEAPPENDPOST = 31,
NeoVimAutoCommandEventFILEAPPENDPRE = 32,
NeoVimAutoCommandEventFILECHANGEDRO = 33,
NeoVimAutoCommandEventFILECHANGEDSHELL = 34,
NeoVimAutoCommandEventFILECHANGEDSHELLPOST = 35,
NeoVimAutoCommandEventFILEREADCMD = 36,
NeoVimAutoCommandEventFILEREADPOST = 37,
NeoVimAutoCommandEventFILEREADPRE = 38,
NeoVimAutoCommandEventFILETYPE = 39,
NeoVimAutoCommandEventFILEWRITECMD = 40,
NeoVimAutoCommandEventFILEWRITEPOST = 41,
NeoVimAutoCommandEventFILEWRITEPRE = 42,
NeoVimAutoCommandEventFILTERREADPOST = 43,
NeoVimAutoCommandEventFILTERREADPRE = 44,
NeoVimAutoCommandEventFILTERWRITEPOST = 45,
NeoVimAutoCommandEventFILTERWRITEPRE = 46,
NeoVimAutoCommandEventFOCUSGAINED = 47,
NeoVimAutoCommandEventFOCUSLOST = 48,
NeoVimAutoCommandEventFUNCUNDEFINED = 49,
NeoVimAutoCommandEventGUIENTER = 50,
NeoVimAutoCommandEventGUIFAILED = 51,
NeoVimAutoCommandEventINSERTCHANGE = 52,
NeoVimAutoCommandEventINSERTCHARPRE = 53,
NeoVimAutoCommandEventINSERTENTER = 54,
NeoVimAutoCommandEventINSERTLEAVE = 55,
NeoVimAutoCommandEventJOBACTIVITY = 56,
NeoVimAutoCommandEventMENUPOPUP = 57,
NeoVimAutoCommandEventOPTIONSET = 58,
NeoVimAutoCommandEventQUICKFIXCMDPOST = 59,
NeoVimAutoCommandEventQUICKFIXCMDPRE = 60,
NeoVimAutoCommandEventQUITPRE = 61,
NeoVimAutoCommandEventREMOTEREPLY = 62,
NeoVimAutoCommandEventSESSIONLOADPOST = 63,
NeoVimAutoCommandEventSHELLCMDPOST = 64,
NeoVimAutoCommandEventSHELLFILTERPOST = 65,
NeoVimAutoCommandEventSOURCECMD = 66,
NeoVimAutoCommandEventSOURCEPRE = 67,
NeoVimAutoCommandEventSPELLFILEMISSING = 68,
NeoVimAutoCommandEventSTDINREADPOST = 69,
NeoVimAutoCommandEventSTDINREADPRE = 70,
NeoVimAutoCommandEventSWAPEXISTS = 71,
NeoVimAutoCommandEventSYNTAX = 72,
NeoVimAutoCommandEventTABCLOSED = 73,
NeoVimAutoCommandEventTABENTER = 74,
NeoVimAutoCommandEventTABLEAVE = 75,
NeoVimAutoCommandEventTABNEW = 76,
NeoVimAutoCommandEventTABNEWENTERED = 77,
NeoVimAutoCommandEventTERMCHANGED = 78,
NeoVimAutoCommandEventTERMCLOSE = 79,
NeoVimAutoCommandEventTERMOPEN = 80,
NeoVimAutoCommandEventTERMRESPONSE = 81,
NeoVimAutoCommandEventTEXTCHANGED = 82,
NeoVimAutoCommandEventTEXTCHANGEDI = 83,
NeoVimAutoCommandEventTEXTYANKPOST = 84,
NeoVimAutoCommandEventUSER = 85,
NeoVimAutoCommandEventVIMENTER = 86,
NeoVimAutoCommandEventVIMLEAVE = 87,
NeoVimAutoCommandEventVIMLEAVEPRE = 88,
NeoVimAutoCommandEventVIMRESIZED = 89,
NeoVimAutoCommandEventWINENTER = 90,
NeoVimAutoCommandEventWINLEAVE = 91
};
#define NumberOfAutoCommandEvents 92

View File

@ -5,6 +5,8 @@
@import Foundation;
#import "NeoVimAutoCommandEvent.generated.h"
// Keep in sync with the constants in vim.h
typedef NS_ENUM(NSUInteger, Mode) {
@ -67,7 +69,7 @@ NS_ASSUME_NONNULL_BEGIN
* 2. NeoVim wants to put the cursor at (row, column).
* In case of 1. NeoVim will put in subsequent call. In case of 2. NeoVim seems to flush twice in a row.
*/
- (void)gotoPosition:(Position)position screenCursor:(Position)screenCursor;
- (void)gotoPosition:(Position)position screenCursor:(Position)screenCursor currentPosition:(Position)currentPosition;
- (void)updateMenu;
- (void)busyStart;
@ -115,8 +117,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)setTitle:(NSString *)title;
- (void)setIcon:(NSString *)icon;
- (void)setDirtyStatus:(bool)dirty;
- (void)cwdChanged;
- (void)bufferListChanged;
- (void)autoCommandEvent:(NeoVimAutoCommandEvent)event bufferHandle:(NSInteger)bufferHandle;
/**
* NeoVim has been stopped.

View File

@ -107,6 +107,8 @@ public class NeoVimView: NSView, NeoVimUiBridgeProtocol, NSUserInterfaceValidati
return true
}
public fileprivate(set) var currentPosition = Position(row: 1, column: 1)
fileprivate static let emojis: [UInt32] = [
0x1F600...0x1F64F,
0x1F910...0x1F918,
@ -208,10 +210,7 @@ public class NeoVimView: NSView, NeoVimUiBridgeProtocol, NSUserInterfaceValidati
@IBAction public func debug1(_ sender: AnyObject?) {
NSLog("DEBUG 1 - Start")
Swift.print("!!!!!!!!!!!!!!!!! \(self.agent.boolOption("paste"))")
self.agent.setBoolOption("paste", to: true)
self.agent.vimInput(self.vimPlainString("foo\nbar"))
self.agent.setBoolOption("paste", to: false)
self.agent.cursorGo(toRow: 10, column: 5)
NSLog("DEBUG 1 - End")
}
@ -336,10 +335,16 @@ extension NeoVimView {
self.exec(command: "qa!")
}
fileprivate func open(_ url: URL, cmd: String) {
let path = url.path
let escapedFileName = self.agent.escapedFileName(path)
self.exec(command: "\(cmd) \(escapedFileName)")
public func vimOutput(of command: String) -> String {
return self.agent.vimCommandOutput(command) ?? ""
}
public func vimExCommand(_ command: String) {
self.agent.vimCommand(command)
}
public func cursorGo(to position: Position) {
self.agent.cursorGo(toRow: Int32(position.row), column: Int32(position.column))
}
/**
@ -358,6 +363,12 @@ extension NeoVimView {
self.agent.vimInput("<Esc>:\(cmd)<CR>")
}
}
fileprivate func open(_ url: URL, cmd: String) {
let path = url.path
let escapedFileName = self.agent.escapedFileName(path)
self.exec(command: "\(cmd) \(escapedFileName)")
}
}
// MARK: - Resizing
@ -1246,8 +1257,9 @@ extension NeoVimView {
}
}
public func gotoPosition(_ position: Position, screenCursor: Position) {
public func gotoPosition(_ position: Position, screenCursor: Position, currentPosition: Position) {
DispatchUtils.gui {
self.currentPosition = currentPosition
// NSLog("\(#function): \(position), \(screenCursor)")
let curScreenCursor = self.grid.screenCursor
@ -1276,6 +1288,9 @@ extension NeoVimView {
self.grid.goto(position)
self.grid.moveCursor(screenCursor)
}
DispatchUtils.gui {
self.delegate?.cursor(to: currentPosition)
}
}
public func updateMenu() {
@ -1310,6 +1325,9 @@ extension NeoVimView {
DispatchUtils.gui {
self.grid.scroll(Int(count))
self.setNeedsDisplay(region: self.grid.region)
// Do not send msgs to agent -> neovim in the delegate method. It causes spinning when you're opening a file with
// existing swap file.
self.delegate?.scroll()
}
}
@ -1429,15 +1447,25 @@ extension NeoVimView {
}
}
public func cwdChanged() {
public func autoCommandEvent(_ event: NeoVimAutoCommandEvent, bufferHandle: Int) {
DispatchUtils.gui {
self.delegate?.cwdChanged()
}
// NSLog("\(event.rawValue) with buffer \(bufferHandle)")
if event == .BUFWINENTER || event == .BUFWINLEAVE {
self.bufferListChanged()
}
public func bufferListChanged() {
DispatchUtils.gui {
self.delegate?.bufferListChanged()
if event == .TABENTER {
self.tabChanged()
}
if event == .CWDCHANGED {
self.cwdChanged()
}
if event == .BUFREADPOST || event == .BUFWRITEPOST {
self.currentBufferChanged(bufferHandle)
}
}
}
@ -1454,6 +1482,30 @@ extension NeoVimView {
}
}
fileprivate func currentBufferChanged(_ handle: Int) {
guard let currentBuffer = self.currentBuffer() else {
return
}
guard currentBuffer.handle == handle else {
return
}
self.delegate?.currentBufferChanged(currentBuffer)
}
fileprivate func tabChanged() {
self.delegate?.tabChanged()
}
fileprivate func cwdChanged() {
self.delegate?.cwdChanged()
}
fileprivate func bufferListChanged() {
self.delegate?.bufferListChanged()
}
fileprivate func updateCursorWhenPutting(currentPosition curPos: Position, screenCursor: Position) {
if self.mode == .Cmdline {
// When the cursor is in the command line, then we need this...

View File

@ -13,6 +13,11 @@ public protocol NeoVimViewDelegate: class {
func set(dirtyStatus: Bool)
func cwdChanged()
func bufferListChanged()
func tabChanged()
func currentBufferChanged(_ currentBuffer: NeoVimBuffer)
func ipcBecameInvalid(reason: String)
func scroll()
func cursor(to: Position)
}

View File

@ -19,3 +19,4 @@ FOUNDATION_EXPORT const unsigned char SwiftNeoVimVersionString[];
#import <SwiftNeoVim/NeoVimBuffer.h>
#import <SwiftNeoVim/NeoVimTab.h>
#import <SwiftNeoVim/NeoVimWindow.h>
#import <SwiftNeoVim/NeoVimAutoCommandEvent.generated.h>

View File

@ -13,6 +13,7 @@
1929B1E05C116514C1D3A384 /* CocoaCategories.m in Sources */ = {isa = PBXBuildFile; fileRef = 1929B5C3F2F1CA4113DABFFD /* CocoaCategories.m */; };
1929B3CEE0C1A1850E9CCE2F /* BasicTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B2FBE11048569391E092 /* BasicTypes.swift */; };
1929B3F5743967125F357C9F /* Matcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BEEB33113B0E33C3830F /* Matcher.swift */; };
1929B4145AA81F006BAF3B5C /* PreviewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BB8BCA48637156F92945 /* PreviewService.swift */; };
1929B462CD4935AFF6D69457 /* FileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B7CB4863F80230C32D3C /* FileItem.swift */; };
1929B53876E6952D378C2B30 /* ScoredFileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BDF9EBAF1D9D44399045 /* ScoredFileItem.swift */; };
1929B6388EAF16C190B82955 /* FileItemIgnorePattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B69499B2569793350CEC /* FileItemIgnorePattern.swift */; };
@ -22,15 +23,23 @@
1929B7A2F2B423AA9740FD45 /* FileUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B5D977261F1EBFA9E8F1 /* FileUtilsTest.swift */; };
1929B93DBAD09835E428F610 /* PrefPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BB251F74BEFC82CEEF84 /* PrefPane.swift */; };
1929BA120290D6A2A61A4468 /* ArrayCommonsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B477E1E62433BC48E10B /* ArrayCommonsTest.swift */; };
1929BA3BB94B77E9AE051FE5 /* PreviewComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B8DA5AA33536F0082200 /* PreviewComponent.swift */; };
1929BCF444CE7F1D14D421DE /* FileItemTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B4778E20696E3AAFB69B /* FileItemTest.swift */; };
1929BD3F9E609BFADB27584B /* Scorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B9D510177918080BE39B /* Scorer.swift */; };
1929BD4CA2204E061A86A140 /* MatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BC19C1BC19246AFF1621 /* MatcherTests.swift */; };
1929BD52275A6570C666A7BA /* PreviewRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1EC32D8A26958FB39B1 /* PreviewRenderer.swift */; };
1929BEB90DCDAF7A2B68C886 /* ColorUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BA6128BFDD54CA92F46E /* ColorUtils.swift */; };
1929BEFEABA0448306CDB6D4 /* FileItemIgnorePatternTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BBC84557C8351EC6183E /* FileItemIgnorePatternTest.swift */; };
1929BF81A40B4154D3EA33CE /* server_ui.m in Sources */ = {isa = PBXBuildFile; fileRef = 1929B93013228985F509C8F6 /* server_ui.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
4B029F1A1D45E349004EE0D3 /* PrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B029F1C1D45E349004EE0D3 /* PrefWindow.xib */; };
4B0677371D99D9C3001A2588 /* FileBrowserComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0677361D99D9C3001A2588 /* FileBrowserComponent.swift */; };
4B0BCC941D70320C00D3CE65 /* Logger.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B0BCC931D70320C00D3CE65 /* Logger.h */; settings = {ATTRIBUTES = (Private, ); }; };
4B183E0E1E06E2940079E8A8 /* CocoaMarkdown.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B183E0D1E06E2940079E8A8 /* CocoaMarkdown.framework */; };
4B183E101E06E29C0079E8A8 /* CocoaMarkdown.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B183E0D1E06E2940079E8A8 /* CocoaMarkdown.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4B183E1B1E08748B0079E8A8 /* NeoVimAutoCommandEvent.generated.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B183E1A1E08748B0079E8A8 /* NeoVimAutoCommandEvent.generated.h */; settings = {ATTRIBUTES = (Public, ); }; };
4B183E1E1E09931E0079E8A8 /* MarkdownRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B183E1D1E09931E0079E8A8 /* MarkdownRenderer.swift */; };
4B19BEFC1E110183007E823C /* markdown in Resources */ = {isa = PBXBuildFile; fileRef = 4B19BEFA1E110183007E823C /* markdown */; };
4B19BEFD1E110183007E823C /* preview in Resources */ = {isa = PBXBuildFile; fileRef = 4B19BEFB1E110183007E823C /* preview */; };
4B22F7F01D7C029400929B0E /* ScorerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B22F7EF1D7C029400929B0E /* ScorerTest.swift */; };
4B22F7F21D7C6B9000929B0E /* ImageAndTextTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B22F7F11D7C6B9000929B0E /* ImageAndTextTableCell.swift */; };
4B238BE11D3BF24200CBDD98 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B238BE01D3BF24200CBDD98 /* Application.swift */; };
@ -192,6 +201,7 @@
4BDD056B1DB0CACB00D1B405 /* Sparkle.framework in Embed Frameworks */,
4BDF50121D760B7200D8FBC3 /* EonilFileSystemEvents.framework in Embed Frameworks */,
4B91FFF61DEB772B00447068 /* CocoaFontAwesome.framework in Embed Frameworks */,
4B183E101E06E29C0079E8A8 /* CocoaMarkdown.framework in Embed Frameworks */,
4B2A2BFF1D0351810074CE9A /* SwiftNeoVim.framework in Embed Frameworks */,
4B2A2BEF1D02261F0074CE9A /* RxSwift.framework in Embed Frameworks */,
4B401B161D0454E900D99EDC /* PureLayout.framework in Embed Frameworks */,
@ -269,6 +279,7 @@
1929B0EEBE4A765934AF8335 /* DataWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataWrapper.h; sourceTree = "<group>"; };
1929B15B7EDC9B0F40E5E95C /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = "<group>"; };
1929B1A51F076E088EF4CCA4 /* server_globals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = server_globals.h; sourceTree = "<group>"; };
1929B1EC32D8A26958FB39B1 /* PreviewRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewRenderer.swift; sourceTree = "<group>"; };
1929B2FBE11048569391E092 /* BasicTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicTypes.swift; sourceTree = "<group>"; };
1929B39DA7AC4A9B62D7CD39 /* Component.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Component.swift; sourceTree = "<group>"; };
1929B3A98687DF171307AAC8 /* FileItemService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemService.swift; sourceTree = "<group>"; };
@ -278,6 +289,7 @@
1929B5D977261F1EBFA9E8F1 /* FileUtilsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtilsTest.swift; sourceTree = "<group>"; };
1929B69499B2569793350CEC /* FileItemIgnorePattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemIgnorePattern.swift; sourceTree = "<group>"; };
1929B7CB4863F80230C32D3C /* FileItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItem.swift; sourceTree = "<group>"; };
1929B8DA5AA33536F0082200 /* PreviewComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewComponent.swift; sourceTree = "<group>"; };
1929B93013228985F509C8F6 /* server_ui.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = server_ui.m; sourceTree = "<group>"; };
1929B9AF20D7BD6E5C975128 /* FoundationCommons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationCommons.swift; sourceTree = "<group>"; };
1929B9D510177918080BE39B /* Scorer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scorer.swift; sourceTree = "<group>"; };
@ -285,6 +297,7 @@
1929BA8AC40B901B20F20B71 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
1929BB251F74BEFC82CEEF84 /* PrefPane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefPane.swift; sourceTree = "<group>"; };
1929BB6CFF4CC0B5E8B00C62 /* DataWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataWrapper.m; sourceTree = "<group>"; };
1929BB8BCA48637156F92945 /* PreviewService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewService.swift; sourceTree = "<group>"; };
1929BBC84557C8351EC6183E /* FileItemIgnorePatternTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemIgnorePatternTest.swift; sourceTree = "<group>"; };
1929BC19C1BC19246AFF1621 /* MatcherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MatcherTests.swift; sourceTree = "<group>"; };
1929BDF9EBAF1D9D44399045 /* ScoredFileItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScoredFileItem.swift; sourceTree = "<group>"; };
@ -294,6 +307,11 @@
4B029F1B1D45E349004EE0D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PrefWindow.xib; sourceTree = "<group>"; };
4B0677361D99D9C3001A2588 /* FileBrowserComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileBrowserComponent.swift; sourceTree = "<group>"; };
4B0BCC931D70320C00D3CE65 /* Logger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logger.h; path = VimR/Logger.h; sourceTree = SOURCE_ROOT; };
4B183E0D1E06E2940079E8A8 /* CocoaMarkdown.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CocoaMarkdown.framework; path = Carthage/Build/Mac/CocoaMarkdown.framework; sourceTree = SOURCE_ROOT; };
4B183E1A1E08748B0079E8A8 /* NeoVimAutoCommandEvent.generated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NeoVimAutoCommandEvent.generated.h; sourceTree = "<group>"; };
4B183E1D1E09931E0079E8A8 /* MarkdownRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownRenderer.swift; sourceTree = "<group>"; };
4B19BEFA1E110183007E823C /* markdown */ = {isa = PBXFileReference; lastKnownFileType = folder; path = markdown; sourceTree = "<group>"; };
4B19BEFB1E110183007E823C /* preview */ = {isa = PBXFileReference; lastKnownFileType = folder; path = preview; sourceTree = "<group>"; };
4B1BB3521D16C5E500CA4FEF /* InputTestView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputTestView.swift; sourceTree = "<group>"; };
4B22F7EF1D7C029400929B0E /* ScorerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScorerTest.swift; sourceTree = "<group>"; };
4B22F7F11D7C6B9000929B0E /* ImageAndTextTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageAndTextTableCell.swift; sourceTree = "<group>"; };
@ -456,6 +474,7 @@
4B2A2BFE1D0351810074CE9A /* SwiftNeoVim.framework in Frameworks */,
4BDD056A1DB0CAB700D1B405 /* Sparkle.framework in Frameworks */,
4B337FBB1DEB76F20020ADD2 /* CocoaFontAwesome.framework in Frameworks */,
4B183E0E1E06E2940079E8A8 /* CocoaMarkdown.framework in Frameworks */,
4B2A2BEE1D02261F0074CE9A /* RxSwift.framework in Frameworks */,
4B401B141D0454DC00D99EDC /* PureLayout.framework in Frameworks */,
4BDF50081D7607BF00D8FBC3 /* EonilFileSystemEvents.framework in Frameworks */,
@ -482,6 +501,16 @@
path = resources;
sourceTree = "<group>";
};
1929BA610ADEA2BA4424FBE5 /* Preview */ = {
isa = PBXGroup;
children = (
4B183E1C1E0992FD0079E8A8 /* Preview Renderers */,
1929B8DA5AA33536F0082200 /* PreviewComponent.swift */,
1929BB8BCA48637156F92945 /* PreviewService.swift */,
);
name = Preview;
sourceTree = "<group>";
};
1929BFC86BF38D341F2DDCBD /* NeoVim Objects */ = {
isa = PBXGroup;
children = (
@ -504,6 +533,15 @@
name = "File Browser";
sourceTree = "<group>";
};
4B183E1C1E0992FD0079E8A8 /* Preview Renderers */ = {
isa = PBXGroup;
children = (
4B183E1D1E09931E0079E8A8 /* MarkdownRenderer.swift */,
1929B1EC32D8A26958FB39B1 /* PreviewRenderer.swift */,
);
name = "Preview Renderers";
sourceTree = "<group>";
};
4B1AC1AF1D7F395300898F0B /* Open Quickly */ = {
isa = PBXGroup;
children = (
@ -534,6 +572,7 @@
4B2A2BE61D0225840074CE9A /* Frameworks */ = {
isa = PBXGroup;
children = (
4B183E0D1E06E2940079E8A8 /* CocoaMarkdown.framework */,
4B337FBA1DEB76F20020ADD2 /* CocoaFontAwesome.framework */,
4BDD05691DB0CAB700D1B405 /* Sparkle.framework */,
4BDF50071D7607BF00D8FBC3 /* EonilFileSystemEvents.framework */,
@ -607,6 +646,7 @@
4B705BA01DDF7639005F844B /* ToolPrefData.swift */,
4B0677351D99D9A2001A2588 /* File Browser */,
4B705C0D1DE2167A005F844B /* BufferListComponent.swift */,
1929BA610ADEA2BA4424FBE5 /* Preview */,
);
name = Tools;
sourceTree = "<group>";
@ -655,6 +695,8 @@
4B97E2CF1D33F92200FC0660 /* resources */ = {
isa = PBXGroup;
children = (
4B19BEFA1E110183007E823C /* markdown */,
4B19BEFB1E110183007E823C /* preview */,
4BEBA50C1CFF374B00673FDF /* MainMenu.xib */,
4B97E2CE1D33F53D00FC0660 /* MainWindow.xib */,
4B029F1C1D45E349004EE0D3 /* PrefWindow.xib */,
@ -789,6 +831,7 @@
4B401B191D046E0600D99EDC /* NeoVimViewDelegate.swift */,
4B570DC01D303CAF006EDC21 /* NeoVimAgent.h */,
4B570DC11D303CAF006EDC21 /* NeoVimAgent.m */,
4B183E1A1E08748B0079E8A8 /* NeoVimAutoCommandEvent.generated.h */,
4B2A2C061D0352CB0074CE9A /* NeoVimUiBridgeProtocol.h */,
);
name = NeoVimView;
@ -820,6 +863,7 @@
4BDD05881DBBC50000D1B405 /* NeoVimTab.h in Headers */,
4BDCFAEA1D31486E00F62670 /* NeoVimMsgIds.h in Headers */,
4B2A2BFA1D0351810074CE9A /* SwiftNeoVim.h in Headers */,
4B183E1B1E08748B0079E8A8 /* NeoVimAutoCommandEvent.generated.h in Headers */,
4B0BCC941D70320C00D3CE65 /* Logger.h in Headers */,
4BDF64241D08CAB000D47E1D /* MMCoreTextView.h in Headers */,
);
@ -832,6 +876,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 4B2A2C021D0351810074CE9A /* Build configuration list for PBXNativeTarget "SwiftNeoVim" */;
buildPhases = (
4B183E191E08746B0079E8A8 /* ShellScript */,
4B2A2BF21D0351810074CE9A /* Sources */,
4B2A2BF31D0351810074CE9A /* Frameworks */,
4B2A2BF41D0351810074CE9A /* Headers */,
@ -925,6 +970,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 4BEBA51D1CFF374B00673FDF /* Build configuration list for PBXNativeTarget "VimR" */;
buildPhases = (
4B19BEFF1E110208007E823C /* ShellScript */,
4BEBA5011CFF374B00673FDF /* Sources */,
4BEBA5021CFF374B00673FDF /* Frameworks */,
4BEBA5031CFF374B00673FDF /* Resources */,
@ -966,7 +1012,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0810;
LastUpgradeCheck = 0810;
LastUpgradeCheck = 0820;
ORGANIZATIONNAME = "Tae Won Ha";
TargetAttributes = {
4B2A2BF61D0351810074CE9A = {
@ -1070,7 +1116,9 @@
4BB409E51DD68CCC005F39A2 /* FileBrowserMenu.xib in Resources */,
4BEBA50E1CFF374B00673FDF /* MainMenu.xib in Resources */,
4B97E2CC1D33F53D00FC0660 /* MainWindow.xib in Resources */,
4B19BEFC1E110183007E823C /* markdown in Resources */,
4B029F1A1D45E349004EE0D3 /* PrefWindow.xib in Resources */,
4B19BEFD1E110183007E823C /* preview in Resources */,
4B3AC8941DB031C600AC5823 /* sparkle_pub.pem in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1086,6 +1134,32 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
4B183E191E08746B0079E8A8 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if [ \"${CONFIGURATION}\" = \"Debug\" ]; then\n if [ -f \"SwiftNeoVim/NeoVimAutoCommandEvent.generated.h\" ]; then\n exit 0\n fi\nfi\n\nsed 's/^typedef enum auto_event/typedef NS_ENUM(NSUInteger, NeoVimAutoCommandEvent)/' <./neovim/build/include/auevents_enum.generated.h | sed 's/ event_T//' | sed 's/EVENT_/NeoVimAutoCommandEvent/' | sed 's/NUM_EVENTS/NumberOfAutoCommandEvents/' | sed -e '1s/^/@import Foundation;\\'$'\\n/' > SwiftNeoVim/NeoVimAutoCommandEvent.generated.h\n";
};
4B19BEFF1E110208007E823C /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cp ${SRCROOT}/Carthage/Checkouts/github-markdown-css/github-markdown.css ${SRCROOT}/VimR/markdown/";
};
4BBA71F11D319E0900E16612 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -1213,9 +1287,13 @@
1929BD3F9E609BFADB27584B /* Scorer.swift in Sources */,
4B6B0A781DA2A1A500212D6D /* FileOutlineView.swift in Sources */,
4BB409EE1DDA77E9005F39A2 /* ProxyWorkspaceBar.swift in Sources */,
4B183E1E1E09931E0079E8A8 /* MarkdownRenderer.swift in Sources */,
4BAD84E81D7CA8FC00A79CC3 /* OpenQuicklyFilterOperation.swift in Sources */,
1929B0E0C3BC59F52713D5A2 /* FoundationCommons.swift in Sources */,
1929B3CEE0C1A1850E9CCE2F /* BasicTypes.swift in Sources */,
1929BA3BB94B77E9AE051FE5 /* PreviewComponent.swift in Sources */,
1929B4145AA81F006BAF3B5C /* PreviewService.swift in Sources */,
1929BD52275A6570C666A7BA /* PreviewRenderer.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0810"
LastUpgradeVersion = "0820"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0810"
LastUpgradeVersion = "0820"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0810"
LastUpgradeVersion = "0820"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0810"
LastUpgradeVersion = "0820"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -80,9 +80,9 @@ class AdvancedPrefPane: PrefPane {
action: #selector(AdvancedPrefPane.useInteractiveZshAction(_:)))
let useInteractiveZshInfo = self.infoTextField(
text: "If your login shell is zsh, when checked, the '-i' option will be used to launch zsh.\n"
+ "Checking this option may break VimR if your .zshrc contains complex stuff.\n"
+ "It may be a good idea to put the PATH-settings in .zshenv and let this unchecked.\n"
markdown: "If your login shell is `zsh`, when checked, the `-i` option will be used to launch `zsh`. \n"
+ "Checking this option may break VimR if your `.zshrc` contains complex stuff. \n"
+ "It may be a good idea to put the `PATH`-settings in `.zshenv` and let this unchecked. \n"
+ "Use with caution."
)
@ -92,7 +92,7 @@ class AdvancedPrefPane: PrefPane {
action: #selector(AdvancedPrefPane.useSnapshotUpdateChannelAction(_:)))
let useSnapshotUpdateInfo = self.infoTextField(
text: "If you are adventurous, check this.\n"
markdown: "If you are adventurous, check this. \n"
+ "You'll be test driving the newest snapshot builds of VimR in no time!"
)

View File

@ -4,6 +4,7 @@
*/
import Cocoa
import CocoaMarkdown
extension NSImage {
@ -33,6 +34,19 @@ extension NSButton {
}
}
extension NSMenuItem {
var boolState: Bool {
get {
return self.state == NSOnState ? true : false
}
set {
self.state = newValue ? NSOnState : NSOffState
}
}
}
extension NSAttributedString {
func draw(at point: CGPoint, angle: CGFloat) {
@ -75,6 +89,32 @@ extension NSAttributedString {
var wholeRange: NSRange {
return NSRange(location: 0, length: self.length)
}
static func infoLabel(markdown: String) -> NSAttributedString {
let size = NSFont.smallSystemFontSize()
let document = CMDocument(data: markdown.data(using: .utf8), options: .normalize)
let attrs = CMTextAttributes()
attrs?.textAttributes = [
NSFontAttributeName: NSFont.systemFont(ofSize: size),
NSForegroundColorAttributeName: NSColor.gray,
]
attrs?.inlineCodeAttributes = [
NSFontAttributeName: NSFont.userFixedPitchFont(ofSize: size)!,
NSForegroundColorAttributeName: NSColor.gray,
]
let renderer = CMAttributedStringRenderer(document: document, attributes: attrs)
renderer?.register(CMHTMLStrikethroughTransformer())
renderer?.register(CMHTMLSuperscriptTransformer())
renderer?.register(CMHTMLUnderlineTransformer())
guard let result = renderer?.render() else {
preconditionFailure("Wrong markdown: \(markdown)")
}
return result
}
}
extension NSView {

View File

@ -12,7 +12,7 @@ enum BufferListAction {
case open(buffer: NeoVimBuffer)
}
class BufferListComponent: ViewComponent, NSTableViewDataSource, NSTableViewDelegate {
class BufferListComponent: StandardViewComponent, NSTableViewDataSource, NSTableViewDelegate {
fileprivate var buffers: [NeoVimBuffer] = []
fileprivate let bufferList = NSTableView.standardTableView()

View File

@ -12,6 +12,11 @@ protocol Flow: class {
var sink: Observable<Any> { get }
}
protocol ViewComponent: Flow {
var view: NSView { get }
}
class PublishingFlow: Flow {
var sink: Observable<Any> {
@ -62,7 +67,7 @@ class EmbeddableComponent: Flow {
fileprivate let subject = PublishSubject<Any>()
fileprivate let source: Observable<Any>
fileprivate let disposeBag = DisposeBag()
let disposeBag = DisposeBag()
init(source: Observable<Any>) {
self.source = source
@ -116,10 +121,11 @@ class StandardComponent: NSObject, Flow {
}
}
class ViewComponent: NSView, Flow {
class StandardViewComponent: NSView, ViewComponent {
var view: NSView {
preconditionFailure("Please override")
return self
}
var sink: Observable<Any> {

View File

@ -46,7 +46,7 @@ struct FileBrowserData: StandardPrefData {
}
}
class FileBrowserComponent: ViewComponent, ToolDataHolder {
class FileBrowserComponent: StandardViewComponent, ToolDataHolder {
fileprivate let fileView: FileOutlineView
fileprivate let fileItemService: FileItemService
@ -99,21 +99,14 @@ class FileBrowserComponent: ViewComponent, ToolDataHolder {
}
fileprivate func addViews() {
let goToParentIcon = NSImage.fontAwesomeIcon(name: .levelUp,
textColor: InnerToolBar.iconColor,
dimension: InnerToolBar.iconDimension)
let goToParent = self.goToParentButton
InnerToolBar.configureToStandardIconButton(button: goToParent, iconName: .levelUp)
goToParent.toolTip = "Set parent as working directory"
InnerToolBar.configureToStandardIconButton(button: goToParent, image: goToParentIcon)
goToParent.action = #selector(FileBrowserComponent.goToParentAction)
let scrollToSourceIcon = NSImage.fontAwesomeIcon(name: .bullseye,
textColor: InnerToolBar.iconColor,
dimension: InnerToolBar.iconDimension)
let scrollToSource = self.scrollToSourceButton
InnerToolBar.configureToStandardIconButton(button: scrollToSource, iconName: .bullseye)
scrollToSource.toolTip = "Navigate to the current buffer"
InnerToolBar.configureToStandardIconButton(button: scrollToSource, image: scrollToSourceIcon)
scrollToSource.action = #selector(FileBrowserComponent.scrollToSourceAction)
self.addSubview(goToParent)
@ -145,7 +138,7 @@ class FileBrowserComponent: ViewComponent, ToolDataHolder {
let showHiddenMenuItem = NSMenuItem(title: "Show Hidden Files",
action: #selector(FileBrowserComponent.showHiddenAction),
keyEquivalent: "")
showHiddenMenuItem.state = initialData.isShowHidden ? NSOnState : NSOffState
showHiddenMenuItem.boolState = initialData.isShowHidden
self.menuItems = [
showHiddenMenuItem,
]

View File

@ -111,8 +111,13 @@ class GeneralPrefPane: PrefPane, NSTextFieldDelegate {
{ [unowned self] _ in
self.ignorePatternsAction()
}
let ignoreInfo = self.infoTextField(text: "")
ignoreInfo.attributedStringValue = self.ignoreInfoText()
let ignoreInfo =
self.infoTextField(markdown:
"Comma-separated list of ignore patterns \n"
+ "Matching files will be ignored in \"Open Quickly\" and the file browser. \n"
+ "Example: `*/.git, */node_modules` \n"
+ "For detailed information see [VimR Wiki](https://github.com/qvacua/vimr/wiki)."
)
let cliToolTitle = self.titleTextField(title: "CLI Tool:")
let cliToolButton = NSButton(forAutoLayout: ())
@ -123,7 +128,7 @@ class GeneralPrefPane: PrefPane, NSTextFieldDelegate {
cliToolButton.target = self
cliToolButton.action = #selector(GeneralPrefPane.copyCliTool(_:))
let cliToolInfo = self.infoTextField(
text: "Put the executable 'vimr' in your $PATH and execute 'vimr -h' for help."
markdown: "Put the executable `vimr` in your `$PATH` and execute `vimr -h` for help."
)
self.addSubview(paneTitle)
@ -203,24 +208,12 @@ class GeneralPrefPane: PrefPane, NSTextFieldDelegate {
}
fileprivate func ignoreInfoText() -> NSAttributedString {
let font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize())
let attrs = [
NSFontAttributeName: font,
NSForegroundColorAttributeName: NSColor.gray
]
let markdown = "Comma-separated list of ignore patterns\n\n"
+ "Matching files will be ignored in \"Open Quickly\" and the file browser.\n\n"
+ "Example: `*/.git, */node_modules`\n\n"
+ "For detailed information see [VimR Wiki](https://github.com/qvacua/vimr/wiki)."
let wikiUrl = URL(string: "https://github.com/qvacua/vimr/wiki")!
let linkStr = NSAttributedString.link(withUrl: wikiUrl, text: "VimR Wiki", font: font)
let str = "Comma-separated list of ignore patterns\n"
+ "Matching files will be ignored in \"Open Quickly\" and the file browser.\n"
+ "Example: */.git, */node_modules\n"
+ "For detailed information see "
let ignoreInfoStr = NSMutableAttributedString(string:str, attributes:attrs)
ignoreInfoStr.append(linkStr)
ignoreInfoStr.append(NSAttributedString(string: ".", attributes: attrs))
return ignoreInfoStr
return NSAttributedString.infoLabel(markdown: markdown)
}
fileprivate func updateViews(newData: GeneralPrefData) {

View File

@ -15,6 +15,9 @@ enum MainWindowAction {
case changeBufferList(mainWindow: MainWindowComponent, buffers: [NeoVimBuffer])
case changeFileBrowserSelection(mainWindow: MainWindowComponent, url: URL)
case close(mainWindow: MainWindowComponent, mainWindowPrefData: MainWindowPrefData)
case toggleTool(tool: WorkspaceTool)
case currentBufferChanged(mainWindow: MainWindowComponent, buffer: NeoVimBuffer)
}
struct MainWindowPrefData: StandardPrefData {
@ -28,6 +31,7 @@ struct MainWindowPrefData: StandardPrefData {
toolPrefDatas: [
ToolPrefData.defaults[.fileBrowser]!,
ToolPrefData.defaults[.bufferList]!,
ToolPrefData.defaults[.preview]!,
])
var isAllToolsVisible: Bool
@ -50,11 +54,10 @@ struct MainWindowPrefData: StandardPrefData {
}
// Add default tool pref data for missing identifiers.
let toolDatas = toolDataDicts.map { ToolPrefData(dict: $0) }.flatMap { $0 }
let toolDatas = toolDataDicts.flatMap { ToolPrefData(dict: $0) }
let missingToolDatas = Set(ToolIdentifier.all)
.subtracting(toolDatas.map { $0.identifier })
.map { ToolPrefData.defaults[$0] }
.flatMap { $0 }
.flatMap { ToolPrefData.defaults[$0] }
self.init(isAllToolsVisible: isAllToolsVisible,
isToolButtonsVisible: isToolButtonsVisible,
@ -70,7 +73,7 @@ struct MainWindowPrefData: StandardPrefData {
}
func toolPrefData(for identifier: ToolIdentifier) -> ToolPrefData {
guard let data = self.toolPrefDatas.filter({ $0.identifier == identifier }).first else {
guard let data = self.toolPrefDatas.first(where: { $0.identifier == identifier }) else {
preconditionFailure("[ERROR] No tool for \(identifier) found!")
}
@ -85,6 +88,12 @@ class MainWindowComponent: WindowComponent,
WorkspaceDelegate
{
enum ScrollAction {
case scroll(to: Position)
case cursor(to: Position)
}
fileprivate static let nibName = "MainWindow"
fileprivate var defaultEditorFont: NSFont
@ -98,6 +107,8 @@ class MainWindowComponent: WindowComponent,
fileprivate let neoVimView: NeoVimView
fileprivate var tools = [ToolIdentifier: WorkspaceToolComponent]()
fileprivate let scrollFlow: EmbeddableComponent
// MARK: - API
var uuid: String {
return self.neoVimView.uuid
@ -141,6 +152,8 @@ class MainWindowComponent: WindowComponent,
self.defaultEditorFont = initialData.appearance.editorFont
self.fileItemService = fileItemService
self.scrollFlow = EmbeddableComponent(source: Observable.empty())
super.init(source: source, nibName: MainWindowComponent.nibName)
self.window.delegate = self
@ -171,6 +184,7 @@ class MainWindowComponent: WindowComponent,
// By default the tool buttons are shown and only the file browser tool is shown.
let fileBrowserToolData = mainWindowData.toolPrefData(for: .fileBrowser)
let bufferListToolData = mainWindowData.toolPrefData(for: .bufferList)
let previewToolData = mainWindowData.toolPrefData(for: .preview)
let fileBrowserData = fileBrowserToolData.toolData as? FileBrowserData ?? FileBrowserData.default
@ -196,11 +210,25 @@ class MainWindowComponent: WindowComponent,
let bufferListTool = WorkspaceToolComponent(toolIdentifier: .bufferList, config: bufferListConfig)
self.tools[.bufferList] = bufferListTool
let previewData = previewToolData.toolData as? PreviewComponent.PrefData ?? PreviewComponent.PrefData.default
let preview = PreviewComponent(source: self.sink,
scrollSource: self.scrollFlow.sink,
initialData: previewData)
let previewConfig = WorkspaceTool.Config(title: "Preview",
view: preview,
minimumDimension: 200,
withInnerToolbar: true)
let previewTool = WorkspaceToolComponent(toolIdentifier: .preview, config: previewConfig)
preview.workspaceTool = previewTool
self.tools[.preview] = previewTool
self.workspace.append(tool: fileBrowserTool, location: fileBrowserToolData.location)
self.workspace.append(tool: bufferListTool, location: bufferListToolData.location)
self.workspace.append(tool: previewTool, location: previewToolData.location)
fileBrowserTool.dimension = fileBrowserToolData.dimension
bufferListTool.dimension = bufferListToolData.dimension
previewTool.dimension = previewToolData.dimension
if !mainWindowData.isAllToolsVisible {
self.toggleAllTools(self)
@ -217,6 +245,10 @@ class MainWindowComponent: WindowComponent,
if bufferListToolData.isVisible {
bufferListTool.toggle()
}
if previewToolData.isVisible {
previewTool.toggle()
}
}
func open(urls: [URL]) {
@ -276,8 +308,16 @@ class MainWindowComponent: WindowComponent,
case let BufferListAction.open(buffer: buffer):
self.neoVimView.select(buffer: buffer)
case let PreviewComponent.Action.reverseSearch(to: position):
self.neoVimView.cursorGo(to: position)
return
case let PreviewComponent.Action.scroll(to: position):
NSLog("preview scrolled to \(position)")
return
default:
NSLog("WARN unrecognized action: \(action)")
NSLog("Not handled action: \(action)")
return
}
@ -321,6 +361,10 @@ extension MainWindowComponent {
func resizeDidEnd(workspace: Workspace) {
self.neoVimView.exitResizeMode()
}
func toggled(tool: WorkspaceTool) {
self.publish(event: MainWindowAction.toggleTool(tool: tool))
}
}
// MARK: - File Menu Item Actions
@ -426,18 +470,18 @@ extension MainWindowComponent {
let fileBrowserTool = self.tools[.fileBrowser]!
if fileBrowserTool.isSelected {
if fileBrowserTool.viewComponent.isFirstResponder {
if fileBrowserTool.viewComponent.view.isFirstResponder {
fileBrowserTool.toggle()
self.focusNeoVimView(self)
} else {
fileBrowserTool.viewComponent.beFirstResponder()
fileBrowserTool.viewComponent.view.beFirstResponder()
}
return
}
fileBrowserTool.toggle()
fileBrowserTool.viewComponent.beFirstResponder()
fileBrowserTool.viewComponent.view.beFirstResponder()
}
@IBAction func focusNeoVimView(_ sender: Any?) {
@ -500,6 +544,18 @@ extension MainWindowComponent {
self.publish(event: MainWindowAction.changeBufferList(mainWindow: self, buffers: buffers))
}
func currentBufferChanged(_ currentBuffer: NeoVimBuffer) {
self.publish(event: MainWindowAction.currentBufferChanged(mainWindow: self, buffer: currentBuffer))
}
func tabChanged() {
guard let currentBuffer = self.neoVimView.currentBuffer() else {
return
}
self.publish(event: MainWindowAction.currentBufferChanged(mainWindow: self, buffer: currentBuffer))
}
func ipcBecameInvalid(reason: String) {
let alert = NSAlert()
alert.addButton(withTitle: "Close")
@ -511,6 +567,14 @@ extension MainWindowComponent {
self?.windowController.close()
}
}
func scroll() {
self.scrollFlow.publish(event: ScrollAction.scroll(to: self.neoVimView.currentPosition))
}
func cursor(to position: Position) {
self.scrollFlow.publish(event: ScrollAction.cursor(to: self.neoVimView.currentPosition))
}
}
// MARK: - NSWindowDelegate
@ -549,7 +613,14 @@ extension MainWindowComponent {
isVisible: bufferList.isSelected,
dimension: bufferList.dimension)
return [ fileBrowserData, bufferListData ]
let preview = self.tools[.preview]!
let previewData = ToolPrefData(identifier: .preview,
location: preview.location,
isVisible: preview.isSelected,
dimension: preview.dimension,
toolData: preview.toolData)
return [ fileBrowserData, bufferListData, previewData ]
}
func windowShouldClose(_ sender: Any) -> Bool {

View File

@ -49,7 +49,7 @@ class MainWindowManager: StandardFlow {
case let .close(mainWindow, mainWindowPrefData):
self.close(mainWindow, prefData: mainWindowPrefData)
case .changeCwd, .changeBufferList, .changeFileBrowserSelection:
default:
break;
}
})

402
VimR/MarkdownRenderer.swift Normal file
View File

@ -0,0 +1,402 @@
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
import RxSwift
import PureLayout
import CocoaMarkdown
import WebKit
fileprivate class WebviewMessageHandler: NSObject, WKScriptMessageHandler {
enum Action {
case scroll(lineBegin: Int, columnBegin: Int, lineEnd: Int, columnEnd: Int)
}
fileprivate let flow: EmbeddableComponent
override init() {
flow = EmbeddableComponent(source: Observable.empty())
super.init()
}
func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) {
guard let msgBody = message.body as? [String: Int] else {
return
}
guard let lineBegin = msgBody["lineBegin"],
let columnBegin = msgBody["columnBegin"],
let lineEnd = msgBody["lineEnd"],
let columnEnd = msgBody["columnEnd"]
else {
return
}
flow.publish(event: Action.scroll(lineBegin: lineBegin, columnBegin: columnBegin,
lineEnd: lineEnd, columnEnd: columnEnd))
}
}
class MarkdownRenderer: NSObject, Flow, PreviewRenderer {
static let identifier = "com.qvacua.vimr.tool.preview.markdown"
static func prefData(from dict: [String: Any]) -> StandardPrefData? {
return PrefData(dict: dict)
}
struct PrefData: StandardPrefData {
fileprivate static let identifier = "identifier"
fileprivate static let isForwardSearchAutomatically = "is-forward-search-automatically"
fileprivate static let isReverseSearchAutomatically = "is-reverse-search-automatically"
fileprivate static let isRefreshOnWrite = "is-refresh-on-write"
static let `default` = PrefData(isForwardSearchAutomatically: false,
isReverseSearchAutomatically: false,
isRefreshOnWrite: true)
var isForwardSearchAutomatically: Bool
var isReverseSearchAutomatically: Bool
var isRefreshOnWrite: Bool
init(isForwardSearchAutomatically: Bool, isReverseSearchAutomatically: Bool, isRefreshOnWrite: Bool) {
self.isForwardSearchAutomatically = isForwardSearchAutomatically
self.isReverseSearchAutomatically = isReverseSearchAutomatically
self.isRefreshOnWrite = isRefreshOnWrite
}
init?(dict: [String: Any]) {
guard PrefUtils.string(from: dict, for: PrefData.identifier) == MarkdownRenderer.identifier else {
return nil
}
guard let isForward = PrefUtils.bool(from: dict, for: PrefData.isForwardSearchAutomatically) else {
return nil
}
guard let isReverse = PrefUtils.bool(from: dict, for: PrefData.isReverseSearchAutomatically) else {
return nil
}
guard let isRefreshOnWrite = PrefUtils.bool(from: dict, for: PrefData.isRefreshOnWrite) else {
return nil
}
self.init(isForwardSearchAutomatically: isForward,
isReverseSearchAutomatically: isReverse,
isRefreshOnWrite: isRefreshOnWrite)
}
func dict() -> [String: Any] {
return [
PrefData.identifier: MarkdownRenderer.identifier,
PrefData.isForwardSearchAutomatically: self.isForwardSearchAutomatically,
PrefData.isReverseSearchAutomatically: self.isReverseSearchAutomatically,
PrefData.isRefreshOnWrite: self.isRefreshOnWrite,
]
}
}
fileprivate let flow: EmbeddableComponent
fileprivate let scrollFlow: EmbeddableComponent
fileprivate let scheduler = ConcurrentDispatchQueueScheduler(qos: .userInitiated)
fileprivate let baseUrl = Bundle.main.resourceURL!.appendingPathComponent("markdown")
fileprivate let extensions = Set(["md", "markdown", ])
fileprivate let template: String
fileprivate let userContentController = WKUserContentController()
fileprivate let webviewMessageHandler = WebviewMessageHandler()
fileprivate var isForwardSearchAutomatically = false
fileprivate var isReverseSearchAutomatically = false
fileprivate var isRefreshOnWrite = true
fileprivate let automaticForwardMenuItem = NSMenuItem(title: "Automatic Forward Search",
action: nil,
keyEquivalent: "")
fileprivate let automaticReverseMenuItem = NSMenuItem(title: "Automatic Reverse Search",
action: nil,
keyEquivalent: "")
fileprivate let refreshOnWriteMenuItem = NSMenuItem(title: "Refresh on Write", action: nil, keyEquivalent: "")
fileprivate let webview: WKWebView
fileprivate var ignoreNextForwardSearch = false;
fileprivate var currentEditorPosition = Position(row: 1, column: 1)
fileprivate var currentUrl: URL?
fileprivate var currentPreviewPosition = Position(row: 1, column: 1)
let identifier: String = MarkdownRenderer.identifier
var prefData: StandardPrefData? {
return PrefData(isForwardSearchAutomatically: self.isForwardSearchAutomatically,
isReverseSearchAutomatically: self.isReverseSearchAutomatically,
isRefreshOnWrite: self.isRefreshOnWrite)
}
var sink: Observable<Any> {
return self.flow.sink
}
var scrollSink: Observable<Any> {
return self.scrollFlow.sink
}
let toolbar: NSView? = NSView(forAutoLayout: ())
let menuItems: [NSMenuItem]?
init(source: Observable<Any>, scrollSource: Observable<Any>, initialData: PrefData) {
guard let templateUrl = Bundle.main.url(forResource: "template",
withExtension: "html",
subdirectory: "markdown")
else {
preconditionFailure("ERROR Cannot load markdown template")
}
guard let template = try? String(contentsOf: templateUrl) else {
preconditionFailure("ERROR Cannot load markdown template")
}
self.template = template
self.flow = EmbeddableComponent(source: source)
self.scrollFlow = EmbeddableComponent(source: scrollSource)
let configuration = WKWebViewConfiguration()
configuration.userContentController = self.userContentController
self.webview = WKWebView(frame: .zero, configuration: configuration)
self.webview.configureForAutoLayout()
self.isForwardSearchAutomatically = initialData.isForwardSearchAutomatically
self.isReverseSearchAutomatically = initialData.isReverseSearchAutomatically
self.isRefreshOnWrite = initialData.isRefreshOnWrite
let refreshMenuItem = NSMenuItem(title: "Refresh Now", action: nil, keyEquivalent: "")
let forwardSearchMenuItem = NSMenuItem(title: "Forward Search", action: nil, keyEquivalent: "")
let reverseSearchMenuItem = NSMenuItem(title: "Reverse Search", action: nil, keyEquivalent: "")
let automaticForward = self.automaticForwardMenuItem
let automaticReverse = self.automaticReverseMenuItem
let refreshOnWrite = self.refreshOnWriteMenuItem
automaticForward.boolState = self.isForwardSearchAutomatically
automaticReverse.boolState = self.isReverseSearchAutomatically
refreshOnWrite.boolState = self.isRefreshOnWrite
self.menuItems = [
refreshMenuItem,
forwardSearchMenuItem,
reverseSearchMenuItem,
NSMenuItem.separator(),
automaticForward,
automaticReverse,
NSMenuItem.separator(),
refreshOnWrite,
]
super.init()
self.initCustomUiElements()
refreshMenuItem.target = self
refreshMenuItem.action = #selector(MarkdownRenderer.refreshNowAction)
forwardSearchMenuItem.target = self
forwardSearchMenuItem.action = #selector(MarkdownRenderer.forwardSearchAction)
reverseSearchMenuItem.target = self
reverseSearchMenuItem.action = #selector(MarkdownRenderer.reverseSearchAction)
automaticForward.target = self
automaticForward.action = #selector(MarkdownRenderer.automaticForwardSearchAction)
automaticReverse.target = self
automaticReverse.action = #selector(MarkdownRenderer.automaticReverseSearchAction)
refreshOnWrite.target = self
refreshOnWrite.action = #selector(MarkdownRenderer.refreshOnWriteAction)
self.flow.set(subscription: self.subscription)
self.scrollFlow.set(subscription: self.scrollSubscription)
self.addReactions()
self.userContentController.add(webviewMessageHandler, name: "com_vimr_preview_markdown")
}
func canRender(fileExtension: String) -> Bool {
return extensions.contains(fileExtension)
}
fileprivate func subscription(source: Observable<Any>) -> Disposable {
return source
.observeOn(self.scheduler)
.mapOmittingNil { (action) -> URL? in
switch action {
case let PreviewComponent.Action.automaticRefresh(url):
guard self.isRefreshOnWrite else {
return nil
}
return url
default:
return nil
}
}
.subscribe(onNext: { [unowned self] url in
self.currentUrl = url
guard self.canRender(fileExtension: url.pathExtension) else {
return
}
self.render(from: url)
})
}
fileprivate func initCustomUiElements() {
let refresh = NSButton(forAutoLayout: ())
InnerToolBar.configureToStandardIconButton(button: refresh, iconName: .refresh)
refresh.toolTip = "Refresh Now"
refresh.target = self
refresh.action = #selector(MarkdownRenderer.refreshNowAction)
let forward = NSButton(forAutoLayout: ())
InnerToolBar.configureToStandardIconButton(button: forward, iconName: .chevronCircleRight)
forward.toolTip = "Forward Search"
forward.target = self
forward.action = #selector(MarkdownRenderer.forwardSearchAction)
let reverse = NSButton(forAutoLayout: ())
InnerToolBar.configureToStandardIconButton(button: reverse, iconName: .chevronCircleLeft)
reverse.toolTip = "Reverse Search"
reverse.target = self
reverse.action = #selector(MarkdownRenderer.reverseSearchAction)
self.toolbar?.addSubview(forward)
self.toolbar?.addSubview(reverse)
self.toolbar?.addSubview(refresh)
forward.autoPinEdge(toSuperviewEdge: .top)
forward.autoPinEdge(toSuperviewEdge: .right)
reverse.autoPinEdge(toSuperviewEdge: .top)
reverse.autoPinEdge(.right, to: .left, of: forward)
refresh.autoPinEdge(toSuperviewEdge: .top)
refresh.autoPinEdge(.right, to: .left, of: reverse)
}
fileprivate func scrollSubscription(source: Observable<Any>) -> Disposable {
return source
.throttle(0.75, latest: true, scheduler: MainScheduler.instance)
.filter { $0 is MainWindowComponent.ScrollAction }
.map { $0 as! MainWindowComponent.ScrollAction }
.subscribe(onNext: { [unowned self] action in
switch action {
case let .scroll(to: position), let .cursor(to: position):
self.currentEditorPosition = position
}
guard self.isForwardSearchAutomatically && self.ignoreNextForwardSearch == false else {
self.ignoreNextForwardSearch = false
return
}
self.forwardSearch(position: self.currentEditorPosition)
})
}
fileprivate func addReactions() {
self.webviewMessageHandler.flow.sink
.throttle(0.75, latest: true, scheduler: MainScheduler.instance)
.filter { $0 is WebviewMessageHandler.Action }
.map { $0 as! WebviewMessageHandler.Action }
.subscribe(onNext: { [weak self] action in
switch action {
case let .scroll(lineBegin, columnBegin, _, _):
self?.currentPreviewPosition = Position(row: lineBegin, column: columnBegin)
guard self?.isReverseSearchAutomatically == true else {
return
}
if self?.isForwardSearchAutomatically == true {
self?.ignoreNextForwardSearch = true
}
self?.flow.publish(
event: PreviewRendererAction.reverseSearch(to: Position(row: lineBegin, column: columnBegin))
)
}
})
.addDisposableTo(self.flow.disposeBag)
}
fileprivate func filledTemplate(body: String, title: String) -> String {
return self.template
.replacingOccurrences(of: "{{ title }}", with: title)
.replacingOccurrences(of: "{{ body }}", with: body)
}
fileprivate func render(from url: URL) {
let doc = CMDocument(contentsOfFile: url.path, options: .sourcepos)
let renderer = CMHTMLRenderer(document: doc)
guard let body = renderer?.render() else {
self.flow.publish(event: PreviewRendererAction.error)
return
}
let html = filledTemplate(body: body, title: url.lastPathComponent)
self.webview.loadHTMLString(html, baseURL: self.baseUrl)
try? html.write(toFile: "/tmp/markdown-preview.html", atomically: false, encoding: .utf8)
self.flow.publish(event: PreviewRendererAction.view(renderer: self, view: self.webview))
}
}
// MARK: - Actions
extension MarkdownRenderer {
fileprivate func forwardSearch(position: Position) {
self.webview.evaluateJavaScript("scrollToPosition(\(position.row), \(position.column));")
}
func refreshNowAction(_: Any?) {
guard let url = self.currentUrl else {
return
}
guard self.canRender(fileExtension: url.pathExtension) else {
return
}
self.render(from: url)
}
func forwardSearchAction(_: Any?) {
self.forwardSearch(position: self.currentEditorPosition)
}
func reverseSearchAction(_: Any?) {
self.flow.publish(event: PreviewRendererAction.reverseSearch(to: self.currentPreviewPosition))
}
func automaticForwardSearchAction(_: Any?) {
self.isForwardSearchAutomatically = !self.isForwardSearchAutomatically
self.automaticForwardMenuItem.boolState = self.isForwardSearchAutomatically
}
func automaticReverseSearchAction(_: Any?) {
self.isReverseSearchAutomatically = !self.isReverseSearchAutomatically
self.automaticReverseMenuItem.boolState = self.isReverseSearchAutomatically
}
func refreshOnWriteAction(_: Any?) {
self.isRefreshOnWrite = !self.isRefreshOnWrite
self.refreshOnWriteMenuItem.boolState = self.isRefreshOnWrite
}
}

View File

@ -6,7 +6,7 @@
import Cocoa
import RxSwift
class PrefPane: ViewComponent {
class PrefPane: StandardViewComponent {
// Return true to place this to the upper left corner when the scroll view is bigger than this view.
override var isFlipped: Bool {
@ -44,19 +44,18 @@ extension PrefPane {
return field
}
func infoTextField(text: String) -> NSTextField {
func infoTextField(markdown: String) -> NSTextField {
let field = NSTextField(forAutoLayout: ())
field.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize())
field.textColor = NSColor.gray
field.backgroundColor = NSColor.clear
field.isEditable = false
field.isBordered = false
field.usesSingleLineMode = false
// both are needed, otherwise hyperlink won't accept mousedown
field.isSelectable = true
field.allowsEditingTextAttributes = true
field.stringValue = text
field.attributedStringValue = NSAttributedString.infoLabel(markdown: markdown)
return field
}

View File

@ -71,6 +71,10 @@ class PrefUtils {
return (dict[key] as? NSNumber)?.boolValue ?? defaultValue
}
static func string(from dict: [String: Any], for key: String) -> String? {
return dict[key] as? String
}
static func saneFont(_ fontName: String, fontSize: CGFloat) -> NSFont {
var editorFont = NSFont(name: fontName, size: fontSize) ?? NeoVimView.defaultFont
if !editorFont.isFixedPitch {

279
VimR/PreviewComponent.swift Normal file
View File

@ -0,0 +1,279 @@
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
import RxSwift
import PureLayout
import WebKit
class PreviewComponent: NSView, ViewComponent, ToolDataHolder {
enum Action {
case automaticRefresh(url: URL)
case reverseSearch(to: Position)
case scroll(to: Position)
}
struct PrefData: StandardPrefData {
fileprivate static let rendererDatas = "renderer-datas"
fileprivate static let rendererPrefDataFns = [
MarkdownRenderer.identifier: MarkdownRenderer.prefData,
]
fileprivate static let rendererDefaultPrefDatas = [
MarkdownRenderer.identifier: MarkdownRenderer.PrefData.default,
]
static let `default` = PrefData(rendererDatas: PrefData.rendererDefaultPrefDatas)
var rendererDatas: [String: StandardPrefData]
init(rendererDatas: [String: StandardPrefData]) {
self.rendererDatas = rendererDatas
}
init?(dict: [String: Any]) {
guard let rendererDataDict = dict[PrefData.rendererDatas] as? [String: [String: Any]] else {
return nil
}
let storedRendererDatas: [(String, StandardPrefData)] = rendererDataDict.flatMap { (identifier, dict) in
guard let prefDataFn = PrefData.rendererPrefDataFns[identifier] else {
return nil
}
guard let prefData = prefDataFn(dict) else {
return nil
}
return (identifier, prefData)
}
let missingRendererDatas: [(String, StandardPrefData)] = Set(PrefData.rendererDefaultPrefDatas.keys)
.subtracting(storedRendererDatas.map { $0.0 })
.flatMap { identifier in
guard let data = PrefData.rendererDefaultPrefDatas[identifier] else {
return nil
}
return (identifier, data)
}
self.init(rendererDatas: toDict([storedRendererDatas, missingRendererDatas].flatMap { $0 }))
}
func dict() -> [String: Any] {
return [
PrefData.rendererDatas: self.rendererDatas.mapToDict { (key, value) in (key, value.dict()) }
]
}
}
fileprivate let scheduler = ConcurrentDispatchQueueScheduler(qos: .userInitiated)
fileprivate let flow: EmbeddableComponent
fileprivate var currentUrl: URL?
fileprivate let renderers: [PreviewRenderer]
fileprivate var currentRenderer: PreviewRenderer? {
didSet {
guard oldValue !== currentRenderer else {
return
}
if let toolbar = self.currentRenderer?.toolbar {
self.workspaceTool?.customInnerToolbar = toolbar
}
if let menuItems = self.currentRenderer?.menuItems {
self.workspaceTool?.customInnerMenuItems = menuItems
}
}
}
fileprivate let markdownRenderer: MarkdownRenderer
fileprivate let baseUrl: URL
fileprivate let webview = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
fileprivate let previewService = PreviewService()
fileprivate var isOpen = false
fileprivate var currentView: NSView {
willSet {
self.currentView.removeFromSuperview()
}
didSet {
self.addSubview(self.currentView)
self.currentView.autoPinEdgesToSuperviewEdges()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
weak var workspaceTool: WorkspaceTool?
var toolData: StandardPrefData {
let rendererDatas = self.renderers.flatMap { (renderer) -> (String, StandardPrefData)? in
guard let data = renderer.prefData else {
return nil
}
return (renderer.identifier, data)
}
return PrefData(rendererDatas: toDict(rendererDatas))
}
var sink: Observable<Any> {
return self.flow.sink
}
var view: NSView {
return self
}
init(source: Observable<Any>, scrollSource: Observable<Any>, initialData: PrefData) {
self.flow = EmbeddableComponent(source: source)
self.baseUrl = self.previewService.baseUrl()
let markdownData = initialData.rendererDatas[MarkdownRenderer.identifier] as? MarkdownRenderer.PrefData ??
MarkdownRenderer.PrefData.default
self.markdownRenderer = MarkdownRenderer(source: self.flow.sink,
scrollSource: scrollSource,
initialData: markdownData)
self.renderers = [
self.markdownRenderer,
]
self.webview.configureForAutoLayout()
self.currentView = self.webview
super.init(frame: .zero)
self.configureForAutoLayout()
self.flow.set(subscription: self.subscription)
self.webview.loadHTMLString(self.previewService.emptyHtml(), baseURL: self.baseUrl)
self.addViews()
self.addReactions()
}
fileprivate func addViews() {
self.addSubview(self.currentView)
self.currentView.autoPinEdgesToSuperviewEdges()
}
fileprivate func subscription(source: Observable<Any>) -> Disposable {
return source
.filter { $0 is MainWindowAction }
.map { $0 as! MainWindowAction }
.subscribe(onNext: { [unowned self] action in
switch action {
case let .currentBufferChanged(_, currentBuffer):
self.currentUrl = currentBuffer.url
guard let url = currentBuffer.url else {
self.currentRenderer = nil
self.currentView = self.webview
self.webview.loadHTMLString(self.previewService.saveFirstHtml(), baseURL: self.baseUrl)
return
}
guard self.isOpen else {
return
}
self.currentRenderer = self.renderers.first { $0.canRender(fileExtension: url.pathExtension) }
if self.currentRenderer == nil {
self.currentView = self.webview
self.webview.loadHTMLString(self.previewService.emptyHtml(), baseURL: self.baseUrl)
}
self.flow.publish(event: PreviewComponent.Action.automaticRefresh(url: url))
case let .toggleTool(tool):
guard tool.view == self else {
return
}
self.isOpen = tool.isSelected
guard let url = self.currentUrl else {
self.currentRenderer = nil
self.currentView = self.webview
self.webview.loadHTMLString(self.previewService.saveFirstHtml(), baseURL: self.baseUrl)
return
}
self.currentRenderer = self.renderers.first { $0.canRender(fileExtension: url.pathExtension) }
if self.currentRenderer != nil {
self.flow.publish(event: PreviewComponent.Action.automaticRefresh(url: url))
}
default:
return
}
})
}
fileprivate func addReactions() {
self.markdownRenderer.scrollSink
.throttle(1, latest: true, scheduler: self.scheduler)
.filter { $0 is PreviewRendererAction }
.map { $0 as! PreviewRendererAction }
.subscribe(onNext: { action in
guard self.isOpen else {
return
}
switch action {
case let .scroll(to: position):
self.flow.publish(event: Action.scroll(to: position))
default:
return
}
})
.addDisposableTo(self.flow.disposeBag)
self.markdownRenderer.sink
.filter { $0 is PreviewRendererAction }
.map { $0 as! PreviewRendererAction }
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [unowned self] action in
guard self.isOpen else {
return
}
switch action {
case let .reverseSearch(to: position):
self.flow.publish(event: Action.reverseSearch(to: position))
case let .htmlString(_, html, baseUrl):
self.webview.loadHTMLString(html, baseURL: baseUrl)
case let .view(_, view):
self.currentView = view
case .error:
self.webview.loadHTMLString(self.previewService.errorHtml(), baseURL: self.baseUrl)
default:
return
}
})
.addDisposableTo(self.flow.disposeBag)
}
}

View File

@ -0,0 +1,33 @@
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
import RxSwift
protocol PreviewRenderer: class {
static var identifier: String { get }
static func prefData(from: [String: Any]) -> StandardPrefData?
var identifier: String { get }
var prefData: StandardPrefData? { get }
var scrollSink: Observable<Any> { get }
var toolbar: NSView? { get }
var menuItems: [NSMenuItem]? { get }
func canRender(fileExtension: String) -> Bool
}
enum PreviewRendererAction {
case htmlString(renderer: PreviewRenderer, html: String, baseUrl: URL)
case view(renderer: PreviewRenderer, view: NSView)
case reverseSearch(to: Position)
case scroll(to: Position)
case error(renderer: PreviewRenderer)
}

62
VimR/PreviewService.swift Normal file
View File

@ -0,0 +1,62 @@
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Foundation
class PreviewService {
fileprivate let empty: String
fileprivate let error: String
fileprivate let saveFirst: String
init() {
guard let emptyUrl = Bundle.main.url(forResource: "empty", withExtension: "html", subdirectory: "preview") else {
preconditionFailure("No empty.html!")
}
guard let errorUrl = Bundle.main.url(forResource: "error", withExtension: "html", subdirectory: "preview") else {
preconditionFailure("No error.html!")
}
guard let saveFirstUrl = Bundle.main.url(forResource: "save-first",
withExtension: "html",
subdirectory: "preview")
else {
preconditionFailure("No save-first.html!")
}
guard let emptyHtml = try? String(contentsOf: emptyUrl) else {
preconditionFailure("Error getting empty.html!")
}
guard let errorHtml = try? String(contentsOf: errorUrl) else {
preconditionFailure("Error getting error.html!")
}
guard let saveFirstHtml = try? String(contentsOf: saveFirstUrl) else {
preconditionFailure("Error getting save-first.html!")
}
self.empty = emptyHtml
self.error = errorHtml
self.saveFirst = saveFirstHtml
}
func baseUrl() -> URL {
return Bundle.main.resourceURL!.appendingPathComponent("preview")
}
func emptyHtml() -> String {
return self.empty
}
func errorHtml() -> String {
return self.error
}
func saveFirstHtml() -> String {
return self.saveFirst
}
}

View File

@ -16,3 +16,13 @@ extension Array {
.flatMap { $0 }
}
}
extension ObservableType {
func mapOmittingNil<R>(_ transform: @escaping (Self.E) throws -> R?) -> RxSwift.Observable<R> {
return self
.map(transform)
.filter { $0 != nil }
.map { $0! }
}
}

View File

@ -56,6 +56,29 @@ extension Array {
}
}
func toDict<K: Hashable, V, S: Sequence>(_ sequence: S) -> Dictionary<K, V> where S.Iterator.Element == (K, V) {
var result = Dictionary<K, V>(minimumCapacity: sequence.underestimatedCount)
for (key, value) in sequence {
result[key] = value
}
return result
}
extension Dictionary {
func mapToDict<T>(_ transform: ((key: Key, value: Value)) throws -> (Key, T)) rethrows -> Dictionary<Key, T> {
let array = try self.map(transform)
return toDict(array)
}
func flatMapToDict<T>(_ transform: ((key: Key, value: Value)) throws -> (Key, T)?) rethrows -> Dictionary<Key, T> {
let array = try self.flatMap(transform)
return toDict(array)
}
}
extension Array where Element: Equatable {
/**
@ -66,7 +89,7 @@ extension Array where Element: Equatable {
*/
func substituting(elements: [Element]) -> [Element] {
let elementsInArray = elements.filter { self.contains($0) }
let indices = elementsInArray.map { self.index(of: $0) }.flatMap { $0 }
let indices = elementsInArray.flatMap { self.index(of: $0) }
var result = self
indices.enumerated().forEach { result[$0.1] = elementsInArray[$0.0] }

View File

@ -9,8 +9,9 @@ enum ToolIdentifier: String {
case fileBrowser = "com.qvacua.vimr.tool.file-browser"
case bufferList = "com.qvacua.vimr.tool.buffer-list"
case preview = "com.qvacua.vimr.tool.preview"
static let all = [ fileBrowser, bufferList ]
static let all = [ fileBrowser, bufferList, preview ]
}
protocol ToolDataHolder: class {
@ -37,6 +38,11 @@ struct ToolPrefData: StandardPrefData {
isVisible: false,
dimension: 200,
toolData: EmptyPrefData.default),
.preview: ToolPrefData(identifier: .preview,
location: .right,
isVisible: false,
dimension: 300,
toolData: PreviewComponent.PrefData.default),
]
var identifier: ToolIdentifier
@ -88,6 +94,8 @@ struct ToolPrefData: StandardPrefData {
switch identifier {
case .fileBrowser:
toolData = FileBrowserData(dict: toolDataDict) ?? FileBrowserData.default
case .preview:
toolData = PreviewComponent.PrefData(dict: toolDataDict) ?? PreviewComponent.PrefData.default
default:
toolData = EmptyPrefData.default
}

View File

@ -24,9 +24,9 @@ class InnerToolBar: NSView, NSUserInterfaceValidations {
fatalError("init(coder:) has not been implemented")
}
fileprivate let titleField = NSTextField(forAutoLayout:())
fileprivate let closeButton = NSButton(forAutoLayout:())
fileprivate let cogButton = NSPopUpButton(forAutoLayout:())
fileprivate let titleField = NSTextField(forAutoLayout: ())
fileprivate let closeButton = NSButton(forAutoLayout: ())
fileprivate let cogButton = NSPopUpButton(forAutoLayout: ())
fileprivate let locToSelector: [WorkspaceBarLocation: Selector] = [
.top: #selector(InnerToolBar.moveToTopAction(_:)),
@ -40,9 +40,13 @@ class InnerToolBar: NSView, NSUserInterfaceValidations {
static let iconDimension = CGFloat(19)
static let iconColor = NSColor.darkGray
static func configureToStandardIconButton(button: NSButton, image: NSImage?) {
static func configureToStandardIconButton(button: NSButton, iconName: CocoaFontAwesome.FontAwesome) {
let icon = NSImage.fontAwesomeIcon(name: iconName,
textColor: InnerToolBar.iconColor,
dimension: InnerToolBar.iconDimension)
button.imagePosition = .imageOnly
button.image = image
button.image = icon
button.isBordered = false
// The following disables the square appearing when pushed.
@ -50,8 +54,18 @@ class InnerToolBar: NSView, NSUserInterfaceValidations {
cell?.highlightsBy = .contentsCellMask
}
let customMenuItems: [NSMenuItem]
var customToolbar: NSView?
var customMenuItems: [NSMenuItem]? {
didSet {
self.removeCustomUiElements()
self.addViews()
}
}
var customToolbar: NSView? {
didSet {
self.removeCustomUiElements()
self.addViews()
}
}
weak var tool: WorkspaceTool? {
didSet {
@ -99,6 +113,11 @@ class InnerToolBar: NSView, NSUserInterfaceValidations {
}
}
fileprivate func removeCustomUiElements() {
[self.titleField, self.customToolbar, self.closeButton, self.cogButton].forEach { $0?.removeFromSuperview() }
self.cogButton.menu = nil
}
fileprivate func addViews() {
let title = self.titleField
let close = self.closeButton
@ -110,18 +129,13 @@ class InnerToolBar: NSView, NSUserInterfaceValidations {
title.isSelectable = false
title.controlSize = .small
let closeIcon = NSImage.fontAwesomeIcon(code: "fa-times-circle",
textColor: InnerToolBar.iconColor,
dimension: InnerToolBar.iconDimension)
InnerToolBar.configureToStandardIconButton(button: close, image: closeIcon)
InnerToolBar.configureToStandardIconButton(button: close, iconName: .timesCircle)
close.target = self
close.action = #selector(InnerToolBar.closeAction)
let cogIcon = NSImage.fontAwesomeIcon(name: .cog,
textColor: InnerToolBar.iconColor,
dimension: InnerToolBar.iconDimension)
cog.configureForAutoLayout()
cog.imagePosition = .imageOnly
cog.pullsDown = true
@ -167,8 +181,8 @@ class InnerToolBar: NSView, NSUserInterfaceValidations {
cogMenu.addItem(cogMenuItem)
if self.customMenuItems.isEmpty == false {
self.customMenuItems.forEach(cogMenu.addItem)
if self.customMenuItems?.isEmpty == false {
self.customMenuItems?.forEach(cogMenu.addItem)
cogMenu.addItem(NSMenuItem.separator())
}

View File

@ -20,6 +20,8 @@ protocol WorkspaceDelegate: class {
func resizeWillStart(workspace: Workspace)
func resizeDidEnd(workspace: Workspace)
func toggled(tool: WorkspaceTool)
}
class Workspace: NSView, WorkspaceBarDelegate {
@ -136,7 +138,6 @@ extension Workspace {
self.isDragOngoing = false
self.draggedOnBarLocation = nil
self.proxyBar.removeFromSuperview()
self.proxyBar.removeAllConstraints()
}
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
@ -196,6 +197,10 @@ extension Workspace {
func resizeDidEnd(workspaceBar: WorkspaceBar) {
self.delegate?.resizeDidEnd(workspace: self)
}
func toggle(tool: WorkspaceTool) {
self.delegate?.toggled(tool: tool)
}
}
// MARK: - Layout

View File

@ -10,6 +10,8 @@ protocol WorkspaceBarDelegate: class {
func resizeWillStart(workspaceBar: WorkspaceBar)
func resizeDidEnd(workspaceBar: WorkspaceBar)
func toggle(tool: WorkspaceTool)
}
/**
@ -113,7 +115,6 @@ class WorkspaceBar: NSView, WorkspaceToolDelegate {
func relayout() {
self.removeConstraints(self.layoutConstraints)
self.proxyBar.removeAllConstraints()
self.removeAllSubviews()
if self.isEmpty() {
@ -835,5 +836,6 @@ extension WorkspaceBar {
self.relayout()
self.delegate?.resizeDidEnd(workspaceBar: self)
self.delegate?.toggle(tool: tool)
}
}

View File

@ -13,6 +13,8 @@ protocol WorkspaceToolDelegate: class {
class WorkspaceTool: NSView {
fileprivate var innerToolbar: InnerToolBar?
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@ -55,11 +57,30 @@ class WorkspaceTool: NSView {
return self.bar?.workspace
}
var innerToolbar: InnerToolBar?
let minimumDimension: CGFloat
var dimension: CGFloat
var customInnerToolbar: NSView? {
get {
return self.innerToolbar?.customToolbar
}
set {
DispatchUtils.gui {
self.innerToolbar?.customToolbar = newValue
}
}
}
var customInnerMenuItems: [NSMenuItem]? {
get {
return self.innerToolbar?.customMenuItems
}
set {
self.innerToolbar?.customMenuItems = newValue
}
}
struct Config {
let title: String
@ -76,8 +97,7 @@ class WorkspaceTool: NSView {
minimumDimension: CGFloat = 50,
withInnerToolbar: Bool = true,
customToolbar: NSView? = nil,
customMenuItems: [NSMenuItem] = [])
{
customMenuItems: [NSMenuItem] = []) {
self.title = title
self.view = view
self.minimumDimension = minimumDimension
@ -110,8 +130,8 @@ class WorkspaceTool: NSView {
}
func toggle() {
self.delegate?.toggle(self)
self.isSelected = !self.isSelected
self.delegate?.toggle(self)
}
fileprivate func addViews() {

View File

@ -0,0 +1,681 @@
@font-face {
font-family: octicons-link;
src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff');
}
.markdown-body {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
line-height: 1.5;
color: #333;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 16px;
line-height: 1.5;
word-wrap: break-word;
}
.markdown-body .pl-c {
color: #969896;
}
.markdown-body .pl-c1,
.markdown-body .pl-s .pl-v {
color: #0086b3;
}
.markdown-body .pl-e,
.markdown-body .pl-en {
color: #795da3;
}
.markdown-body .pl-smi,
.markdown-body .pl-s .pl-s1 {
color: #333;
}
.markdown-body .pl-ent {
color: #63a35c;
}
.markdown-body .pl-k {
color: #a71d5d;
}
.markdown-body .pl-s,
.markdown-body .pl-pds,
.markdown-body .pl-s .pl-pse .pl-s1,
.markdown-body .pl-sr,
.markdown-body .pl-sr .pl-cce,
.markdown-body .pl-sr .pl-sre,
.markdown-body .pl-sr .pl-sra {
color: #183691;
}
.markdown-body .pl-v {
color: #ed6a43;
}
.markdown-body .pl-id {
color: #b52a1d;
}
.markdown-body .pl-ii {
color: #f8f8f8;
background-color: #b52a1d;
}
.markdown-body .pl-sr .pl-cce {
font-weight: bold;
color: #63a35c;
}
.markdown-body .pl-ml {
color: #693a17;
}
.markdown-body .pl-mh,
.markdown-body .pl-mh .pl-en,
.markdown-body .pl-ms {
font-weight: bold;
color: #1d3e81;
}
.markdown-body .pl-mq {
color: #008080;
}
.markdown-body .pl-mi {
font-style: italic;
color: #333;
}
.markdown-body .pl-mb {
font-weight: bold;
color: #333;
}
.markdown-body .pl-md {
color: #bd2c00;
background-color: #ffecec;
}
.markdown-body .pl-mi1 {
color: #55a532;
background-color: #eaffea;
}
.markdown-body .pl-mdr {
font-weight: bold;
color: #795da3;
}
.markdown-body .pl-mo {
color: #1d3e81;
}
.markdown-body .octicon {
display: inline-block;
vertical-align: text-top;
fill: currentColor;
}
.markdown-body a {
background-color: transparent;
-webkit-text-decoration-skip: objects;
}
.markdown-body a:active,
.markdown-body a:hover {
outline-width: 0;
}
.markdown-body strong {
font-weight: inherit;
}
.markdown-body strong {
font-weight: bolder;
}
.markdown-body h1 {
font-size: 2em;
margin: 0.67em 0;
}
.markdown-body img {
border-style: none;
}
.markdown-body svg:not(:root) {
overflow: hidden;
}
.markdown-body code,
.markdown-body kbd,
.markdown-body pre {
font-family: monospace, monospace;
font-size: 1em;
}
.markdown-body hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
.markdown-body input {
font: inherit;
margin: 0;
}
.markdown-body input {
overflow: visible;
}
.markdown-body [type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
.markdown-body * {
box-sizing: border-box;
}
.markdown-body input {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
.markdown-body a {
color: #4078c0;
text-decoration: none;
}
.markdown-body a:hover,
.markdown-body a:active {
text-decoration: underline;
}
.markdown-body strong {
font-weight: 600;
}
.markdown-body hr {
height: 0;
margin: 15px 0;
overflow: hidden;
background: transparent;
border: 0;
border-bottom: 1px solid #ddd;
}
.markdown-body hr::before {
display: table;
content: "";
}
.markdown-body hr::after {
display: table;
clear: both;
content: "";
}
.markdown-body table {
border-spacing: 0;
border-collapse: collapse;
}
.markdown-body td,
.markdown-body th {
padding: 0;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body h1 {
font-size: 32px;
font-weight: 600;
}
.markdown-body h2 {
font-size: 24px;
font-weight: 600;
}
.markdown-body h3 {
font-size: 20px;
font-weight: 600;
}
.markdown-body h4 {
font-size: 16px;
font-weight: 600;
}
.markdown-body h5 {
font-size: 14px;
font-weight: 600;
}
.markdown-body h6 {
font-size: 12px;
font-weight: 600;
}
.markdown-body p {
margin-top: 0;
margin-bottom: 10px;
}
.markdown-body blockquote {
margin: 0;
}
.markdown-body ul,
.markdown-body ol {
padding-left: 0;
margin-top: 0;
margin-bottom: 0;
}
.markdown-body ol ol,
.markdown-body ul ol {
list-style-type: lower-roman;
}
.markdown-body ul ul ol,
.markdown-body ul ol ol,
.markdown-body ol ul ol,
.markdown-body ol ol ol {
list-style-type: lower-alpha;
}
.markdown-body dd {
margin-left: 0;
}
.markdown-body code {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px;
}
.markdown-body pre {
margin-top: 0;
margin-bottom: 0;
font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
.markdown-body .octicon {
vertical-align: text-bottom;
}
.markdown-body input {
-webkit-font-feature-settings: "liga" 0;
font-feature-settings: "liga" 0;
}
.markdown-body::before {
display: table;
content: "";
}
.markdown-body::after {
display: table;
clear: both;
content: "";
}
.markdown-body>*:first-child {
margin-top: 0 !important;
}
.markdown-body>*:last-child {
margin-bottom: 0 !important;
}
.markdown-body a:not([href]) {
color: inherit;
text-decoration: none;
}
.markdown-body .anchor {
float: left;
padding-right: 4px;
margin-left: -20px;
line-height: 1;
}
.markdown-body .anchor:focus {
outline: none;
}
.markdown-body p,
.markdown-body blockquote,
.markdown-body ul,
.markdown-body ol,
.markdown-body dl,
.markdown-body table,
.markdown-body pre {
margin-top: 0;
margin-bottom: 16px;
}
.markdown-body hr {
height: 0.25em;
padding: 0;
margin: 24px 0;
background-color: #e7e7e7;
border: 0;
}
.markdown-body blockquote {
padding: 0 1em;
color: #777;
border-left: 0.25em solid #ddd;
}
.markdown-body blockquote>:first-child {
margin-top: 0;
}
.markdown-body blockquote>:last-child {
margin-bottom: 0;
}
.markdown-body kbd {
display: inline-block;
padding: 3px 5px;
font-size: 11px;
line-height: 10px;
color: #555;
vertical-align: middle;
background-color: #fcfcfc;
border: solid 1px #ccc;
border-bottom-color: #bbb;
border-radius: 3px;
box-shadow: inset 0 -1px 0 #bbb;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
line-height: 1.25;
}
.markdown-body h1 .octicon-link,
.markdown-body h2 .octicon-link,
.markdown-body h3 .octicon-link,
.markdown-body h4 .octicon-link,
.markdown-body h5 .octicon-link,
.markdown-body h6 .octicon-link {
color: #000;
vertical-align: middle;
visibility: hidden;
}
.markdown-body h1:hover .anchor,
.markdown-body h2:hover .anchor,
.markdown-body h3:hover .anchor,
.markdown-body h4:hover .anchor,
.markdown-body h5:hover .anchor,
.markdown-body h6:hover .anchor {
text-decoration: none;
}
.markdown-body h1:hover .anchor .octicon-link,
.markdown-body h2:hover .anchor .octicon-link,
.markdown-body h3:hover .anchor .octicon-link,
.markdown-body h4:hover .anchor .octicon-link,
.markdown-body h5:hover .anchor .octicon-link,
.markdown-body h6:hover .anchor .octicon-link {
visibility: visible;
}
.markdown-body h1 {
padding-bottom: 0.3em;
font-size: 2em;
border-bottom: 1px solid #eee;
}
.markdown-body h2 {
padding-bottom: 0.3em;
font-size: 1.5em;
border-bottom: 1px solid #eee;
}
.markdown-body h3 {
font-size: 1.25em;
}
.markdown-body h4 {
font-size: 1em;
}
.markdown-body h5 {
font-size: 0.875em;
}
.markdown-body h6 {
font-size: 0.85em;
color: #777;
}
.markdown-body ul,
.markdown-body ol {
padding-left: 2em;
}
.markdown-body ul ul,
.markdown-body ul ol,
.markdown-body ol ol,
.markdown-body ol ul {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body li>p {
margin-top: 16px;
}
.markdown-body li+li {
margin-top: 0.25em;
}
.markdown-body dl {
padding: 0;
}
.markdown-body dl dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: bold;
}
.markdown-body dl dd {
padding: 0 16px;
margin-bottom: 16px;
}
.markdown-body table {
display: block;
width: 100%;
overflow: auto;
}
.markdown-body table th {
font-weight: bold;
}
.markdown-body table th,
.markdown-body table td {
padding: 6px 13px;
border: 1px solid #ddd;
}
.markdown-body table tr {
background-color: #fff;
border-top: 1px solid #ccc;
}
.markdown-body table tr:nth-child(2n) {
background-color: #f8f8f8;
}
.markdown-body img {
max-width: 100%;
box-sizing: content-box;
background-color: #fff;
}
.markdown-body code {
padding: 0;
padding-top: 0.2em;
padding-bottom: 0.2em;
margin: 0;
font-size: 85%;
background-color: rgba(0,0,0,0.04);
border-radius: 3px;
}
.markdown-body code::before,
.markdown-body code::after {
letter-spacing: -0.2em;
content: "\00a0";
}
.markdown-body pre {
word-wrap: normal;
}
.markdown-body pre>code {
padding: 0;
margin: 0;
font-size: 100%;
word-break: normal;
white-space: pre;
background: transparent;
border: 0;
}
.markdown-body .highlight {
margin-bottom: 16px;
}
.markdown-body .highlight pre {
margin-bottom: 0;
word-break: normal;
}
.markdown-body .highlight pre,
.markdown-body pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f7f7f7;
border-radius: 3px;
}
.markdown-body pre code {
display: inline;
max-width: auto;
padding: 0;
margin: 0;
overflow: visible;
line-height: inherit;
word-wrap: normal;
background-color: transparent;
border: 0;
}
.markdown-body pre code::before,
.markdown-body pre code::after {
content: normal;
}
.markdown-body .pl-0 {
padding-left: 0 !important;
}
.markdown-body .pl-1 {
padding-left: 3px !important;
}
.markdown-body .pl-2 {
padding-left: 6px !important;
}
.markdown-body .pl-3 {
padding-left: 12px !important;
}
.markdown-body .pl-4 {
padding-left: 24px !important;
}
.markdown-body .pl-5 {
padding-left: 36px !important;
}
.markdown-body .pl-6 {
padding-left: 48px !important;
}
.markdown-body .full-commit .btn-outline:not(:disabled):hover {
color: #4078c0;
border: 1px solid #4078c0;
}
.markdown-body kbd {
display: inline-block;
padding: 3px 5px;
font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
line-height: 10px;
color: #555;
vertical-align: middle;
background-color: #fcfcfc;
border: solid 1px #ccc;
border-bottom-color: #bbb;
border-radius: 3px;
box-shadow: inset 0 -1px 0 #bbb;
}
.markdown-body :checked+.radio-label {
position: relative;
z-index: 1;
border-color: #4078c0;
}
.markdown-body .task-list-item {
list-style-type: none;
}
.markdown-body .task-list-item+.task-list-item {
margin-top: 3px;
}
.markdown-body .task-list-item input {
margin: 0 0.2em 0.25em -1.6em;
vertical-align: middle;
}
.markdown-body hr {
border-bottom-color: #eee;
}

154
VimR/markdown/template.html Normal file
View File

@ -0,0 +1,154 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="github-markdown.css">
<script>
// Scrolling
const sourceposRegex = /(\d+):(\d+)-(\d+):(\d+)/;
let suppressNextScrollEvent = false;
function isTopLeftInsideViewport(el) {
const rect = el.getBoundingClientRect();
return rect.top >= 0 && rect.left >= 0;
}
function isElementVisibleInViewport(el) {
const rect = el.getBoundingClientRect();
return (
((rect.top >= 0 && window.innerHeight - rect.top >= 0) || (rect.top < 0 && rect.bottom > 0)) &&
((rect.left >= 0 && window.innerWidth - rect.left >= 0) || (rect.left < 0 && rect.right > 0))
);
}
function positionOfMarkdownElement(element) {
const regexResult = element.dataset.sourcepos.match(sourceposRegex);
if (regexResult.length != 5) {
return null;
}
return {
lineBegin: parseInt(regexResult[1], 10),
columnBegin: parseInt(regexResult[2], 10),
lineEnd: parseInt(regexResult[3], 10),
columnEnd: parseInt(regexResult[4], 10)
};
}
function toArray(nodeList) {
return Array.prototype.slice.call(nodeList)
}
function currentTopMarkdownElement() {
const mdElements = toArray(document.querySelectorAll("[data-sourcepos]"));
return mdElements.find(isTopLeftInsideViewport) || mdElements.find(isElementVisibleInViewport);
}
let lastMarkdownPosition = {
lineBegin: 1,
columnBegin: 1,
lineEnd: 1,
columnEnd: 1
};
function scrollCallback() {
const candidate = currentTopMarkdownElement();
if (!candidate) {
return;
}
const result = positionOfMarkdownElement(candidate);
if (result.lineBegin == lastMarkdownPosition.lineBegin
&& result.columnBegin == lastMarkdownPosition.columnBegin
&& result.lineEnd == lastMarkdownPosition.lineEnd
&& result.columnEnd == lastMarkdownPosition.columnEnd)
{
return;
}
lastMarkdownPosition = result;
// console.log(`webview scrolled to ${result.lineBegin}:${result.columnBegin}`);
window.webkit.messageHandlers.com_vimr_preview_markdown.postMessage(result);
}
let lastKnownScrollPos = 0;
let ticking = false;
window.addEventListener('scroll', () => {
lastKnownScrollPos = window.scrollY;
if (!ticking) {
window.requestAnimationFrame(() => {
if (lastKnownScrollPos >= 0 && !suppressNextScrollEvent) {
scrollCallback();
}
suppressNextScrollEvent = false;
ticking = false;
});
}
ticking = true;
});
// Forward search
function scrollToPosition(row, column) {
const entries = toArray(document.querySelectorAll("[data-sourcepos]"))
.map(element => {
const position = positionOfMarkdownElement(element);
return {
element: element,
position: position,
rowDistance: Math.abs(row - position.lineBegin),
columnDistance: Math.abs(column - position.columnBegin)
};
});
const minRowDistance = entries.reduce((result, entry) => {
return Math.min(result, entry.rowDistance)
}, Number.MAX_SAFE_INTEGER);
const entriesWithMinRowDistance = entries.filter(entry => entry.rowDistance == minRowDistance);
const minColumnDistance = entriesWithMinRowDistance.reduce((result, entry) => {
return Math.min(result, entry.columnDistance)
}, Number.MAX_SAFE_INTEGER);
const candidateEntry = entriesWithMinRowDistance.find(entry => entry.columnDistance == minColumnDistance);
if (!candidateEntry) {
return;
}
const element = candidateEntry.element;
if (isElementVisibleInViewport(element)) {
return;
}
let {x, y} = scrollPosition(element);
suppressNextScrollEvent = true;
window.scrollTo(x, y);
// console.log(`scrolled webview to ${x}:${y}`);
}
function scrollPosition(element) {
let {x, y} = { x: 0, y: 0 };
let curEl = element;
while (curEl) {
x += curEl.offsetLeft;
y += curEl.offsetTop;
curEl = curEl.offsetParent;
}
return { x: x, y: y };
}
</script>
<title>{{ title }}</title>
</head>
<body class="markdown-body">
{{ body }}
</body>
</html>

62
VimR/preview/base.css Normal file
View File

@ -0,0 +1,62 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
font-family: -apple-system, sans-serif;
}
#message {
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.info-text {
font-size: small;
color: gray;
}

15
VimR/preview/empty.html Normal file
View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="../github-markdown.css">
<link rel="stylesheet" href="base.css">
<title>Empty</title>
</head>
<body>
<div id="message">
<h1>😶</h1>
<span class="info-text">no preview...</span>
</div>
</body>
</html>

15
VimR/preview/error.html Normal file
View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="../github-markdown.css">
<link rel="stylesheet" href="base.css">
<title>Error</title>
</head>
<body>
<div id="message">
<h1>😱</h1>
<span class="info-text">There was an error...</span>
</div>
</body>
</html>

View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="../github-markdown.css">
<link rel="stylesheet" href="base.css">
<title>Error</title>
</head>
<body>
<div id="message">
<h1>😶</h1>
<span class="info-text">Save first for preview</span>
</div>
</body>
</html>