mirror of
https://github.com/qvacua/vimr.git
synced 2024-12-24 22:33:52 +03:00
Merge branch 'issue/339-md-preview' into develop
This commit is contained in:
commit
aba4fbec2f
2
Cartfile
2
Cartfile
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
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;
|
||||
if (event == EVENT_TEXTCHANGED
|
||||
|| event == EVENT_TEXTCHANGEDI
|
||||
|| event == EVENT_BUFWRITEPOST
|
||||
|| event == EVENT_BUFLEAVE)
|
||||
{
|
||||
send_dirty_status();
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
97
SwiftNeoVim/NeoVimAutoCommandEvent.generated.h
Normal file
97
SwiftNeoVim/NeoVimAutoCommandEvent.generated.h
Normal 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
|
@ -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.
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -349,7 +354,7 @@ extension NeoVimView {
|
||||
|
||||
We don't use NeoVimAgent.vimCommand because if we do for example "e /some/file" and its swap file already exists,
|
||||
then NeoVimServer spins and become unresponsive.
|
||||
*/
|
||||
*/
|
||||
fileprivate func exec(command cmd: String) {
|
||||
switch self.mode {
|
||||
case .Normal:
|
||||
@ -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)")
|
||||
|
||||
public func bufferListChanged() {
|
||||
DispatchUtils.gui {
|
||||
self.delegate?.bufferListChanged()
|
||||
if event == .BUFWINENTER || event == .BUFWINLEAVE {
|
||||
self.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...
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0810"
|
||||
LastUpgradeVersion = "0820"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0810"
|
||||
LastUpgradeVersion = "0820"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0810"
|
||||
LastUpgradeVersion = "0820"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0810"
|
||||
LastUpgradeVersion = "0820"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -80,10 +80,10 @@ 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"
|
||||
+ "Use with caution."
|
||||
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."
|
||||
)
|
||||
|
||||
let useSnapshotUpdate = self.useSnapshotUpdateCheckbox
|
||||
@ -92,8 +92,8 @@ class AdvancedPrefPane: PrefPane {
|
||||
action: #selector(AdvancedPrefPane.useSnapshotUpdateChannelAction(_:)))
|
||||
|
||||
let useSnapshotUpdateInfo = self.infoTextField(
|
||||
text: "If you are adventurous, check this.\n"
|
||||
+ "You'll be test driving the newest snapshot builds of VimR in no time!"
|
||||
markdown: "If you are adventurous, check this. \n"
|
||||
+ "You'll be test driving the newest snapshot builds of VimR in no time!"
|
||||
)
|
||||
|
||||
self.addSubview(paneTitle)
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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> {
|
||||
|
@ -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,
|
||||
]
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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
402
VimR/MarkdownRenderer.swift
Normal 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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -130,6 +130,6 @@ class PrefStore: StandardFlow {
|
||||
|
||||
self.userDefaults.setValue(self.data.dict(), forKey: PrefStore.compatibleVersion)
|
||||
self.publish(event: self.data)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
279
VimR/PreviewComponent.swift
Normal 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)
|
||||
}
|
||||
}
|
33
VimR/PreviewRenderer.swift
Normal file
33
VimR/PreviewRenderer.swift
Normal 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
62
VimR/PreviewService.swift
Normal 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
|
||||
}
|
||||
}
|
@ -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! }
|
||||
}
|
||||
}
|
||||
|
@ -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] }
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
681
VimR/markdown/github-markdown.css
Normal file
681
VimR/markdown/github-markdown.css
Normal 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
154
VimR/markdown/template.html
Normal 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
62
VimR/preview/base.css
Normal 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
15
VimR/preview/empty.html
Normal 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
15
VimR/preview/error.html
Normal 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>
|
15
VimR/preview/save-first.html
Normal file
15
VimR/preview/save-first.html
Normal 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>
|
Loading…
Reference in New Issue
Block a user