mirror of
https://github.com/qvacua/vimr.git
synced 2024-11-28 02:54:31 +03:00
Merge remote-tracking branch 'origin/develop' into update-neovim
Conflicts: Cartfile Cartfile.resolved
This commit is contained in:
commit
2caeb2c10e
8
Cartfile
8
Cartfile
@ -1,14 +1,14 @@
|
||||
github "ReactiveX/RxSwift" "4.1.2"
|
||||
github "ReactiveX/RxSwift" "4.2.0"
|
||||
github "PureLayout/PureLayout" == 3.0.2
|
||||
github "eonil/FileSystemEvents" == 1.0.0
|
||||
github "sparkle-project/Sparkle" == 1.19.0
|
||||
github "qvacua/CocoaFontAwesome" "master"
|
||||
github "qvacua/CocoaMarkdown" "master"
|
||||
github "qvacua/RxMsgpackRpc" == 0.0.1
|
||||
github "qvacua/RxMessagePort" == 0.0.1
|
||||
github "qvacua/RxMsgpackRpc" == 0.0.2
|
||||
github "qvacua/RxMessagePort" == 0.0.2
|
||||
github "qvacua/RxNeovimApi" "develop"
|
||||
github "sindresorhus/github-markdown-css" == 2.10.0
|
||||
github "httpswift/swifter" == 1.4.1
|
||||
github "httpswift/swifter" == 1.4.2
|
||||
github "a2/MessagePack.swift" == 3.0.0
|
||||
|
||||
github "Quick/Nimble" == 7.1.2
|
||||
|
@ -1,13 +1,13 @@
|
||||
github "PureLayout/PureLayout" "v3.0.2"
|
||||
github "Quick/Nimble" "v7.1.2"
|
||||
github "ReactiveX/RxSwift" "4.1.2"
|
||||
github "ReactiveX/RxSwift" "4.2.0"
|
||||
github "a2/MessagePack.swift" "3.0.0"
|
||||
github "eonil/FileSystemEvents" "1.0.0"
|
||||
github "httpswift/swifter" "1.4.1"
|
||||
github "httpswift/swifter" "1.4.2"
|
||||
github "qvacua/CocoaFontAwesome" "76cf6c4ef3088d84f78988183c56fc6abdc19f83"
|
||||
github "qvacua/CocoaMarkdown" "7756ad96d5fb390c66531004868e828bb54d3609"
|
||||
github "qvacua/RxMessagePort" "v0.0.1"
|
||||
github "qvacua/RxMsgpackRpc" "v0.0.1"
|
||||
github "qvacua/RxNeovimApi" "c21467e1442326bdf4715539300f315947f49e64"
|
||||
github "qvacua/RxMessagePort" "v0.0.2"
|
||||
github "qvacua/RxMsgpackRpc" "v0.0.2"
|
||||
github "qvacua/RxNeovimApi" "640236a82d195f060a7964dd3ea572e614f18191"
|
||||
github "sindresorhus/github-markdown-css" "v2.10.0"
|
||||
github "sparkle-project/Sparkle" "1.19.0"
|
||||
|
@ -28,7 +28,6 @@
|
||||
#import <nvim/syntax.h>
|
||||
#import <nvim/aucmd.h>
|
||||
#import <nvim/msgpack_rpc/helpers.h>
|
||||
#import <msgpack.h>
|
||||
#import <nvim/api/private/helpers.h>
|
||||
|
||||
|
||||
@ -236,7 +235,9 @@ static void run_neovim(void *arg) {
|
||||
|
||||
argv[0] = "nvim";
|
||||
for (var i = 0; i < nvimArgs.count; i++) {
|
||||
argv[i + 1] = (char *) nvimArgs[(NSUInteger) i].cstr;
|
||||
char *str = (char *) nvimArgs[(NSUInteger) i].cstr;
|
||||
argv[i + 1] = malloc(strlen(str) + 1);
|
||||
strcpy(argv[i + 1], str);
|
||||
}
|
||||
|
||||
[nvimArgs release]; // retained in start_neovim()
|
||||
@ -244,6 +245,9 @@ static void run_neovim(void *arg) {
|
||||
|
||||
nvim_main(argc, argv);
|
||||
|
||||
for (var i = 0; i < argc - 1; i++) {
|
||||
free(argv[i + 1]);
|
||||
}
|
||||
free(argv);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
1929B8CA73D3702364903BB7 /* SimpleCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD86668017F804408E3A /* SimpleCache.swift */; };
|
||||
1929BA70C221E3C199833B8C /* UiBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B52174EC68D2974B5BAE /* UiBridge.swift */; };
|
||||
1929BA93BDEA029011F034FF /* RxSwiftCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B6F4B70B90F7CFB7B523 /* RxSwiftCommons.swift */; };
|
||||
1929BCA615324C58582BFC3C /* ProcessUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD167BE7C6BB788DAE2A /* ProcessUtils.swift */; };
|
||||
4B177886201220F300E32FF0 /* SharedTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 1929B4F32708E99C40A57020 /* SharedTypes.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
4B17E549209E3E4100265C1D /* RxNeovimApi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B17E548209E3E4100265C1D /* RxNeovimApi.framework */; };
|
||||
4B8662E81FDC3F9F007F490D /* vimr.vim in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B8662E41FDC3D4F007F490D /* vimr.vim */; };
|
||||
@ -100,6 +101,7 @@
|
||||
1929B52174EC68D2974B5BAE /* UiBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UiBridge.swift; sourceTree = "<group>"; };
|
||||
1929B6F4B70B90F7CFB7B523 /* RxSwiftCommons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxSwiftCommons.swift; sourceTree = "<group>"; };
|
||||
1929BBD7F88AE4F01E626691 /* NvimApiExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NvimApiExtension.swift; sourceTree = "<group>"; };
|
||||
1929BD167BE7C6BB788DAE2A /* ProcessUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessUtils.swift; sourceTree = "<group>"; };
|
||||
1929BD86668017F804408E3A /* SimpleCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleCache.swift; sourceTree = "<group>"; };
|
||||
4B17E548209E3E4100265C1D /* RxNeovimApi.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxNeovimApi.framework; path = ../Carthage/Build/Mac/RxNeovimApi.framework; sourceTree = "<group>"; };
|
||||
4B8662E41FDC3D4F007F490D /* vimr.vim */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = vimr.vim; sourceTree = "<group>"; };
|
||||
@ -228,6 +230,7 @@
|
||||
1929B6F4B70B90F7CFB7B523 /* RxSwiftCommons.swift */,
|
||||
1929B44323D6611E2927EC3B /* MessagePackCommons.swift */,
|
||||
1929BD86668017F804408E3A /* SimpleCache.swift */,
|
||||
1929BD167BE7C6BB788DAE2A /* ProcessUtils.swift */,
|
||||
);
|
||||
path = NvimView;
|
||||
sourceTree = "<group>";
|
||||
@ -409,6 +412,7 @@
|
||||
1929BA93BDEA029011F034FF /* RxSwiftCommons.swift in Sources */,
|
||||
1929B30D6C4175835D1F5B21 /* MessagePackCommons.swift in Sources */,
|
||||
1929B8CA73D3702364903BB7 /* SimpleCache.swift in Sources */,
|
||||
1929BCA615324C58582BFC3C /* ProcessUtils.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -466,7 +470,7 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 282;
|
||||
CURRENT_PROJECT_VERSION = 284;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@ -526,7 +530,7 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 282;
|
||||
CURRENT_PROJECT_VERSION = 284;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@ -553,7 +557,7 @@
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 282;
|
||||
DYLIB_CURRENT_VERSION = 284;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac";
|
||||
FRAMEWORK_VERSION = A;
|
||||
@ -575,7 +579,7 @@
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 282;
|
||||
DYLIB_CURRENT_VERSION = 284;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac";
|
||||
FRAMEWORK_VERSION = A;
|
||||
|
@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.24.0</string>
|
||||
<string>SNAPSHOT-284</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>282</string>
|
||||
<string>284</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2017 Tae Won Ha. All rights reserved.</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
@ -3,9 +3,12 @@
|
||||
|
||||
void recurseDraw(
|
||||
const unichar *chars,
|
||||
CGGlyph *glyphs, CGPoint *positions, UniCharCount length,
|
||||
CGGlyph *glyphs,
|
||||
CGPoint *positions,
|
||||
UniCharCount length,
|
||||
CGContextRef context,
|
||||
CTFontRef fontRef,
|
||||
NSMutableArray *fontCache,
|
||||
BOOL isComposing,
|
||||
BOOL useLigatures
|
||||
);
|
||||
|
@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extracted from snapshot-146 of MacVim
|
||||
* Extracted from 351faf929e4abe32ea4cc31078d1a625fc86a69f of MacVim, 2018-07-03
|
||||
* https://github.com/macvim-dev/macvim
|
||||
* See VIM.LICENSE
|
||||
*/
|
||||
@ -20,7 +20,9 @@
|
||||
|
||||
#import "MMCoreTextView.h"
|
||||
|
||||
static CTFontRef
|
||||
// @formatter:off
|
||||
|
||||
static CTFontRef
|
||||
lookupFont(NSMutableArray *fontCache, const unichar *chars, UniCharCount count,
|
||||
CTFontRef currFontRef)
|
||||
{
|
||||
@ -61,7 +63,7 @@ attributedStringForString(NSString *string, const CTFontRef font,
|
||||
// 2 - full ligatures including rare
|
||||
// 1 - basic ligatures
|
||||
// 0 - no ligatures
|
||||
[NSNumber numberWithInteger:(useLigatures ? 1 : 0)],
|
||||
[NSNumber numberWithBool:useLigatures],
|
||||
kCTLigatureAttributeName,
|
||||
nil
|
||||
];
|
||||
@ -72,7 +74,7 @@ attributedStringForString(NSString *string, const CTFontRef font,
|
||||
|
||||
static UniCharCount
|
||||
fetchGlyphsAndAdvances(const CTLineRef line, CGGlyph *glyphs, CGSize *advances,
|
||||
UniCharCount length)
|
||||
CGPoint *positions, UniCharCount length)
|
||||
{
|
||||
NSArray *glyphRuns = (NSArray*)CTLineGetGlyphRuns(line);
|
||||
|
||||
@ -82,19 +84,23 @@ fetchGlyphsAndAdvances(const CTLineRef line, CGGlyph *glyphs, CGSize *advances,
|
||||
CTRunRef run = (CTRunRef)item;
|
||||
CFIndex count = CTRunGetGlyphCount(run);
|
||||
|
||||
if (count > 0 && count - offset > length)
|
||||
count = length - offset;
|
||||
if (count > 0) {
|
||||
if (count > length - offset)
|
||||
count = length - offset;
|
||||
|
||||
CFRange range = CFRangeMake(0, count);
|
||||
CFRange range = CFRangeMake(0, count);
|
||||
|
||||
if (glyphs != NULL)
|
||||
CTRunGetGlyphs(run, range, &glyphs[offset]);
|
||||
if (advances != NULL)
|
||||
CTRunGetAdvances(run, range, &advances[offset]);
|
||||
if (glyphs != NULL)
|
||||
CTRunGetGlyphs(run, range, &glyphs[offset]);
|
||||
if (advances != NULL)
|
||||
CTRunGetAdvances(run, range, &advances[offset]);
|
||||
if (positions != NULL)
|
||||
CTRunGetPositions(run, range, &positions[offset]);
|
||||
|
||||
offset += count;
|
||||
if (offset >= length)
|
||||
break;
|
||||
offset += count;
|
||||
if (offset >= length)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
@ -117,78 +123,28 @@ gatherGlyphs(CGGlyph glyphs[], UniCharCount count)
|
||||
}
|
||||
|
||||
static UniCharCount
|
||||
ligatureGlyphsForChars(const unichar *chars, CGGlyph *glyphs,
|
||||
CGPoint *positions, UniCharCount length, CTFontRef font)
|
||||
composeGlyphsForChars(const unichar *chars, CGGlyph *glyphs,
|
||||
CGPoint *positions, UniCharCount length, CTFontRef font,
|
||||
BOOL isComposing, BOOL useLigatures)
|
||||
{
|
||||
// CoreText has no simple wait of retrieving a ligature for a set of
|
||||
// UniChars. The way proposed on the CoreText ML is to convert the text to
|
||||
// an attributed string, create a CTLine from it and retrieve the Glyphs
|
||||
// from the CTRuns in it.
|
||||
CGGlyph refGlyphs[length];
|
||||
CGPoint refPositions[length];
|
||||
|
||||
memcpy(refGlyphs, glyphs, sizeof(CGGlyph) * length);
|
||||
memcpy(refPositions, positions, sizeof(CGSize) * length);
|
||||
|
||||
memset(glyphs, 0, sizeof(CGGlyph) * length);
|
||||
|
||||
NSString *plainText = [NSString stringWithCharacters:chars length:length];
|
||||
CFAttributedStringRef ligatureText = attributedStringForString(plainText,
|
||||
font, YES);
|
||||
CFAttributedStringRef composedText = attributedStringForString(plainText,
|
||||
font,
|
||||
useLigatures);
|
||||
|
||||
CTLineRef ligature = CTLineCreateWithAttributedString(ligatureText);
|
||||
CTLineRef line = CTLineCreateWithAttributedString(composedText);
|
||||
|
||||
CGSize ligatureRanges[length], regularRanges[length];
|
||||
// get the (composing)glyphs and advances for the new text
|
||||
UniCharCount offset = fetchGlyphsAndAdvances(line, glyphs, NULL,
|
||||
isComposing ? positions : NULL,
|
||||
length);
|
||||
|
||||
// get the (ligature)glyphs and advances for the new text
|
||||
UniCharCount offset = fetchGlyphsAndAdvances(ligature, glyphs,
|
||||
ligatureRanges, length);
|
||||
// fetch the advances for the base text
|
||||
CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, refGlyphs,
|
||||
regularRanges, length);
|
||||
CFRelease(composedText);
|
||||
CFRelease(line);
|
||||
|
||||
CFRelease(ligatureText);
|
||||
CFRelease(ligature);
|
||||
|
||||
// tricky part: compare both advance ranges and chomp positions which are
|
||||
// covered by a single ligature while keeping glyphs not in the ligature
|
||||
// font.
|
||||
#define fequal(a, b) (fabs((a) - (b)) < FLT_EPSILON)
|
||||
#define fless(a, b)((a) - (b) < FLT_EPSILON) && (fabs((a) - (b)) > FLT_EPSILON)
|
||||
|
||||
CFIndex skip = 0;
|
||||
CFIndex i;
|
||||
for (i = 0; i < offset && skip + i < length; ++i) {
|
||||
memcpy(&positions[i], &refPositions[skip + i], sizeof(CGSize));
|
||||
|
||||
if (fequal(ligatureRanges[i].width, regularRanges[skip + i].width)) {
|
||||
// [mostly] same width
|
||||
continue;
|
||||
} else if (fless(ligatureRanges[i].width,
|
||||
regularRanges[skip + i].width)) {
|
||||
// original is wider than our result - use the original glyph
|
||||
// FIXME: this is currently the only way to detect emoji (except
|
||||
// for 'glyph[i] == 5')
|
||||
glyphs[i] = refGlyphs[skip + i];
|
||||
continue;
|
||||
}
|
||||
|
||||
// no, that's a ligature
|
||||
// count how many positions this glyph would take up in the base text
|
||||
CFIndex j = 0;
|
||||
float width = ceil(regularRanges[skip + i].width);
|
||||
|
||||
while ((int)width < (int)ligatureRanges[i].width
|
||||
&& skip + i + j < length) {
|
||||
width += ceil(regularRanges[++j + skip + i].width);
|
||||
}
|
||||
skip += j;
|
||||
}
|
||||
|
||||
#undef fless
|
||||
#undef fequal
|
||||
|
||||
// as ligatures combine characters it is required to adjust the
|
||||
// as ligatures composing characters it is required to adjust the
|
||||
// original length value
|
||||
return offset;
|
||||
}
|
||||
@ -196,18 +152,12 @@ ligatureGlyphsForChars(const unichar *chars, CGGlyph *glyphs,
|
||||
void
|
||||
recurseDraw(const unichar *chars, CGGlyph *glyphs, CGPoint *positions,
|
||||
UniCharCount length, CGContextRef context, CTFontRef fontRef,
|
||||
NSMutableArray *fontCache, BOOL useLigatures)
|
||||
NSMutableArray *fontCache, BOOL isComposing, BOOL useLigatures)
|
||||
{
|
||||
if (CTFontGetGlyphsForCharacters(fontRef, chars, glyphs, length)) {
|
||||
// All chars were mapped to glyphs, so draw all at once and return.
|
||||
if (useLigatures) {
|
||||
length = ligatureGlyphsForChars(chars, glyphs, positions, length,
|
||||
fontRef);
|
||||
} else {
|
||||
// only fixup surrogate pairs if we're not using ligatures
|
||||
length = gatherGlyphs(glyphs, length);
|
||||
}
|
||||
|
||||
length = composeGlyphsForChars(chars, glyphs, positions, length,
|
||||
fontRef, isComposing, useLigatures);
|
||||
CTFontDrawGlyphs(fontRef, glyphs, positions, length, context);
|
||||
return;
|
||||
}
|
||||
@ -268,7 +218,7 @@ recurseDraw(const unichar *chars, CGGlyph *glyphs, CGPoint *positions,
|
||||
return;
|
||||
|
||||
recurseDraw(chars, glyphs, positions, attemptedCount, context,
|
||||
fallback, fontCache, useLigatures);
|
||||
fallback, fontCache, isComposing, useLigatures);
|
||||
|
||||
// If only a portion of the invalid range was rendered above,
|
||||
// the remaining range needs to be attempted by subsequent
|
||||
@ -292,4 +242,6 @@ recurseDraw(const unichar *chars, CGGlyph *glyphs, CGPoint *positions,
|
||||
}
|
||||
}
|
||||
|
||||
// @formatter:on
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
@ -29,13 +29,13 @@ extension Api {
|
||||
if checkBlocked {
|
||||
return self
|
||||
.checkBlocked(
|
||||
self.rpc(method: "nvim_buf_get_info", params: params, expectsReturnValue: true)
|
||||
self.rpc(method: "nvim_buf_get_info", params: params)
|
||||
)
|
||||
.map(transform)
|
||||
}
|
||||
|
||||
return self
|
||||
.rpc(method: "nvim_buf_get_info", params: params, expectsReturnValue: true)
|
||||
.rpc(method: "nvim_buf_get_info", params: params)
|
||||
.map(transform)
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ extension NvimView {
|
||||
|
||||
public func newTab() -> Completable {
|
||||
return self.api
|
||||
.command(command: "tabe", expectsReturnValue: false)
|
||||
.command(command: "tabe")
|
||||
.subscribeOn(self.scheduler)
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ extension NvimView {
|
||||
let bufExists = buffers.contains { $0.url == url }
|
||||
let wins = tabs.map({ $0.windows }).flatMap({ $0 })
|
||||
if let win = bufExists ? wins.first(where: { win in win.buffer.url == url }) : nil {
|
||||
return self.api.setCurrentWin(window: Api.Window(win.handle), expectsReturnValue: false)
|
||||
return self.api.setCurrentWin(window: Api.Window(win.handle))
|
||||
}
|
||||
|
||||
return currentBufferIsTransient ? self.open(url, cmd: "e") : self.open(url, cmd: "tabe")
|
||||
@ -112,43 +112,47 @@ extension NvimView {
|
||||
.map { tabs in tabs.map { $0.windows }.flatMap { $0 } }
|
||||
.flatMapCompletable { wins -> Completable in
|
||||
if let win = wins.first(where: { $0.buffer == buffer }) {
|
||||
return self.api.setCurrentWin(window: Api.Window(win.handle), expectsReturnValue: false)
|
||||
return self.api.setCurrentWin(window: Api.Window(win.handle))
|
||||
}
|
||||
|
||||
return self.api.command(command: "tab sb \(buffer.handle)", expectsReturnValue: false)
|
||||
return self.api.command(command: "tab sb \(buffer.handle)")
|
||||
}
|
||||
.subscribeOn(self.scheduler)
|
||||
}
|
||||
|
||||
public func goTo(line: Int) -> Completable {
|
||||
return self.api.command(command: "\(line)")
|
||||
}
|
||||
|
||||
/// Closes the current window.
|
||||
public func closeCurrentTab() -> Completable {
|
||||
return self.api
|
||||
.command(command: "q", expectsReturnValue: true)
|
||||
.command(command: "q")
|
||||
.subscribeOn(self.scheduler)
|
||||
}
|
||||
|
||||
public func saveCurrentTab() -> Completable {
|
||||
return self.api
|
||||
.command(command: "w", expectsReturnValue: true)
|
||||
.command(command: "w")
|
||||
.subscribeOn(self.scheduler)
|
||||
}
|
||||
|
||||
public func saveCurrentTab(url: URL) -> Completable {
|
||||
return self.api
|
||||
.command(command: "w \(url.path)", expectsReturnValue: true)
|
||||
.command(command: "w \(url.path)")
|
||||
.subscribeOn(self.scheduler)
|
||||
}
|
||||
|
||||
public func closeCurrentTabWithoutSaving() -> Completable {
|
||||
return self.api
|
||||
.command(command: "q!", expectsReturnValue: true)
|
||||
.command(command: "q!")
|
||||
.subscribeOn(self.scheduler)
|
||||
}
|
||||
|
||||
public func quitNeoVimWithoutSaving() -> Completable {
|
||||
self.bridgeLogger.mark()
|
||||
return self.api
|
||||
.command(command: "qa!", expectsReturnValue: true)
|
||||
.command(command: "qa!")
|
||||
.subscribeOn(self.scheduler)
|
||||
}
|
||||
|
||||
@ -200,7 +204,7 @@ extension NvimView {
|
||||
|
||||
private func `open`(_ url: URL, cmd: String) -> Completable {
|
||||
return self.api
|
||||
.command(command: "\(cmd) \(url.path)", expectsReturnValue: false)
|
||||
.command(command: "\(cmd) \(url.path)")
|
||||
.subscribeOn(self.scheduler)
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ extension NvimView: NSTouchBarDelegate, NSScrubberDataSource, NSScrubberDelegate
|
||||
|
||||
let window = tab.currentWindow ?? tab.windows[0]
|
||||
self.api
|
||||
.setCurrentWin(window: Api.Window(window.handle), expectsReturnValue: false)
|
||||
.setCurrentWin(window: Api.Window(window.handle))
|
||||
.subscribeOn(self.scheduler)
|
||||
.subscribe(onError: { error in
|
||||
self.eventsSubject.onNext(.apiError(msg: "Could not set current window to \(window.handle).", cause: error))
|
||||
|
@ -17,14 +17,17 @@ public class NvimView: NSView,
|
||||
var useInteractiveZsh: Bool
|
||||
var cwd: URL
|
||||
var nvimArgs: [String]?
|
||||
var envDict: [String: String]?
|
||||
|
||||
public init(useInteractiveZsh: Bool,
|
||||
cwd: URL = URL(fileURLWithPath: NSHomeDirectory()),
|
||||
nvimArgs: [String]? = nil) {
|
||||
cwd: URL,
|
||||
nvimArgs: [String]?,
|
||||
envDict: [String: String]?) {
|
||||
|
||||
self.useInteractiveZsh = useInteractiveZsh
|
||||
self.cwd = cwd
|
||||
self.nvimArgs = nvimArgs
|
||||
self.envDict = envDict
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,7 +174,7 @@ public class NvimView: NSView,
|
||||
|
||||
set {
|
||||
self.api
|
||||
.setCurrentDir(dir: newValue.path, expectsReturnValue: false)
|
||||
.setCurrentDir(dir: newValue.path)
|
||||
.subscribeOn(self.scheduler)
|
||||
.subscribe(onError: { error in
|
||||
self.eventsSubject.onError(Error.ipc(msg: "Could not set cwd to \(newValue)", cause: error))
|
||||
@ -310,7 +313,10 @@ public class NvimView: NSView,
|
||||
}
|
||||
|
||||
convenience override public init(frame rect: NSRect) {
|
||||
self.init(frame: rect, config: Config(useInteractiveZsh: false))
|
||||
self.init(frame: rect, config: Config(useInteractiveZsh: false,
|
||||
cwd: URL(fileURLWithPath: NSHomeDirectory()),
|
||||
nvimArgs: nil,
|
||||
envDict: nil))
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
|
51
NvimView/NvimView/ProcessUtils.swift
Normal file
51
NvimView/NvimView/ProcessUtils.swift
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
class ProcessUtils {
|
||||
|
||||
static func envVars(of shellPath: URL, usingInteractiveMode: Bool) -> [String: String] {
|
||||
let shellName = shellPath.lastPathComponent
|
||||
var shellArgs = [String]()
|
||||
|
||||
if shellName != "tcsh" {
|
||||
shellArgs.append("-l")
|
||||
}
|
||||
|
||||
if usingInteractiveMode {
|
||||
shellArgs.append("-i")
|
||||
}
|
||||
|
||||
shellArgs.append(contentsOf: ["-c", "env"])
|
||||
|
||||
let outputPipe = Pipe()
|
||||
let errorPipe = Pipe()
|
||||
|
||||
let process = Process()
|
||||
process.launchPath = shellPath.path
|
||||
process.arguments = shellArgs
|
||||
process.standardOutput = outputPipe
|
||||
process.standardError = errorPipe
|
||||
process.currentDirectoryPath = NSHomeDirectory()
|
||||
process.launch()
|
||||
|
||||
let readHandle = outputPipe.fileHandleForReading
|
||||
guard let output = String(data: readHandle.readDataToEndOfFile(), encoding: .utf8) else {
|
||||
return [:]
|
||||
}
|
||||
readHandle.closeFile()
|
||||
|
||||
process.terminate()
|
||||
process.waitUntilExit()
|
||||
|
||||
return output
|
||||
.split(separator: "\n")
|
||||
.reduce(into: [:]) { result, entry in
|
||||
let split = entry.split(separator: "=", maxSplits: 1, omittingEmptySubsequences: false).map { String($0) }
|
||||
result[split[0]] = split[1]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
//
|
||||
// Created by Tae Won Ha on 21.05.18.
|
||||
// Copyright (c) 2018 Tae Won Ha. All rights reserved.
|
||||
//
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
|
@ -192,7 +192,7 @@ static CGColorRef color_for(NSInteger value) {
|
||||
const UniChar *bEnd = unichars + unilength;
|
||||
UniCharCount choppedLength;
|
||||
bool wide;
|
||||
bool pWide = NO;
|
||||
bool pWide = false;
|
||||
|
||||
while (b < bEnd) {
|
||||
wide = CFStringIsSurrogateHighCharacter(*b) || CFStringIsSurrogateLowCharacter(*b);
|
||||
@ -200,7 +200,8 @@ static CGColorRef color_for(NSInteger value) {
|
||||
choppedLength = b - bStart;
|
||||
// NSString *logged = [NSString stringWithCharacters:bStart length:choppedLength];
|
||||
// NSLog(@"C(%d,%p..%p)[%@]", pWide, bStart, b, logged);
|
||||
recurseDraw(bStart, glyphs, p, choppedLength, context, fontWithTraits, _fontLookupCache, _usesLigatures);
|
||||
// We use isComposing = false to retain the old behavior of Macvim's recurseDraw
|
||||
recurseDraw(bStart, glyphs, p, choppedLength, context, fontWithTraits, _fontLookupCache, false, _usesLigatures);
|
||||
UniCharCount step = pWide ? choppedLength / 2 : choppedLength;
|
||||
p += step;
|
||||
g += step;
|
||||
@ -214,7 +215,8 @@ static CGColorRef color_for(NSInteger value) {
|
||||
choppedLength = b - bStart;
|
||||
// NSString *logged = [NSString stringWithCharacters:bStart length:choppedLength];
|
||||
// NSLog(@"T(%d,%p..%p)[%@]", pWide, bStart, b, logged);
|
||||
recurseDraw(bStart, glyphs, p, choppedLength, context, fontWithTraits, _fontLookupCache, _usesLigatures);
|
||||
// We use isComposing = false to retain the old behavior of Macvim's recurseDraw
|
||||
recurseDraw(bStart, glyphs, p, choppedLength, context, fontWithTraits, _fontLookupCache, false, _usesLigatures);
|
||||
}
|
||||
// NSLog(@"S(-,%p..%p)[%@]", unichars, unichars + unilength, string);
|
||||
|
||||
|
@ -64,6 +64,17 @@ class UiBridge {
|
||||
self.nvimArgs = config.nvimArgs ?? []
|
||||
self.cwd = config.cwd
|
||||
|
||||
if let envDict = config.envDict {
|
||||
self.envDict = envDict
|
||||
logger.debug("using envs from vimr: \(envDict)")
|
||||
} else {
|
||||
let selfEnv = ProcessInfo.processInfo.environment
|
||||
let shellUrl = URL(fileURLWithPath: selfEnv["SHELL"] ?? "/bin/bash")
|
||||
let interactiveMode = shellUrl.lastPathComponent == "zsh" && !config.useInteractiveZsh ? false : true
|
||||
self.envDict = ProcessUtils.envVars(of: shellUrl, usingInteractiveMode: interactiveMode)
|
||||
logger.debug("using envs from login shell: \(self.envDict)")
|
||||
}
|
||||
|
||||
self.queue = queue
|
||||
self.scheduler = SerialDispatchQueueScheduler(queue: queue,
|
||||
internalSerialQueueName: String(reflecting: UiBridge.self))
|
||||
@ -71,13 +82,13 @@ class UiBridge {
|
||||
self.server.queue = self.queue
|
||||
|
||||
self.server.stream
|
||||
.subscribe(onNext: { message in
|
||||
self.handleMessage(msgId: message.msgid, data: message.data)
|
||||
}, onError: { error in
|
||||
self.logger.error("There was an error on the local message port server: \(error)")
|
||||
self.streamSubject.onError(Error.ipc(error))
|
||||
})
|
||||
.disposed(by: self.disposeBag)
|
||||
.subscribe(onNext: { message in
|
||||
self.handleMessage(msgId: message.msgid, data: message.data)
|
||||
}, onError: { error in
|
||||
self.logger.error("There was an error on the local message port server: \(error)")
|
||||
self.streamSubject.onError(Error.ipc(error))
|
||||
})
|
||||
.disposed(by: self.disposeBag)
|
||||
}
|
||||
|
||||
func runLocalServerAndNvim(width: Int, height: Int) -> Completable {
|
||||
@ -85,15 +96,15 @@ class UiBridge {
|
||||
self.initialHeight = height
|
||||
|
||||
return self.server
|
||||
.run(as: self.localServerName)
|
||||
.andThen(Completable.create { completable in
|
||||
self.runLocalServerAndNvimCompletable = completable
|
||||
self.launchNvimUsingLoginShell()
|
||||
.run(as: self.localServerName)
|
||||
.andThen(Completable.create { completable in
|
||||
self.runLocalServerAndNvimCompletable = completable
|
||||
self.launchNvimUsingLoginShellEnv()
|
||||
|
||||
// This will be completed in .nvimReady branch of handleMessage()
|
||||
return Disposables.create()
|
||||
})
|
||||
.timeout(timeout, scheduler: self.scheduler)
|
||||
// This will be completed in .nvimReady branch of handleMessage()
|
||||
return Disposables.create()
|
||||
})
|
||||
.timeout(timeout, scheduler: self.scheduler)
|
||||
}
|
||||
|
||||
func vimInput(_ str: String) -> Completable {
|
||||
@ -150,11 +161,11 @@ class UiBridge {
|
||||
|
||||
case .serverReady:
|
||||
self
|
||||
.establishNvimConnection()
|
||||
.subscribe(onError: { error in
|
||||
self.streamSubject.onError(Error.ipc(error))
|
||||
})
|
||||
.disposed(by: self.disposeBag)
|
||||
.establishNvimConnection()
|
||||
.subscribe(onError: { error in
|
||||
self.streamSubject.onError(Error.ipc(error))
|
||||
})
|
||||
.disposed(by: self.disposeBag)
|
||||
|
||||
case .nvimReady:
|
||||
self.runLocalServerAndNvimCompletable?(.completed)
|
||||
@ -310,7 +321,7 @@ class UiBridge {
|
||||
let dict = (try? unpack(d))?.value.dictionaryValue,
|
||||
let key = dict.keys.first?.stringValue,
|
||||
let value = dict.values.first
|
||||
else {
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -331,31 +342,31 @@ class UiBridge {
|
||||
|
||||
private func closePorts() -> Completable {
|
||||
return self.client
|
||||
.stop()
|
||||
.andThen(self.server.stop())
|
||||
.stop()
|
||||
.andThen(self.server.stop())
|
||||
}
|
||||
|
||||
private func quit(using body: @escaping () -> Void) -> Completable {
|
||||
return self
|
||||
.closePorts()
|
||||
.andThen(Completable.create { completable in
|
||||
body()
|
||||
.closePorts()
|
||||
.andThen(Completable.create { completable in
|
||||
body()
|
||||
|
||||
completable(.completed)
|
||||
return Disposables.create()
|
||||
})
|
||||
completable(.completed)
|
||||
return Disposables.create()
|
||||
})
|
||||
}
|
||||
|
||||
private func establishNvimConnection() -> Completable {
|
||||
return self.client
|
||||
.connect(to: self.remoteServerName)
|
||||
.andThen(self.sendMessage(msgId: .agentReady, data: [self.initialWidth, self.initialHeight].data()))
|
||||
.connect(to: self.remoteServerName)
|
||||
.andThen(self.sendMessage(msgId: .agentReady, data: [self.initialWidth, self.initialHeight].data()))
|
||||
}
|
||||
|
||||
private func sendMessage(msgId: NvimBridgeMsgId, data: Data?) -> Completable {
|
||||
return self.client
|
||||
.send(msgid: Int32(msgId.rawValue), data: data, expectsReply: false)
|
||||
.asCompletable()
|
||||
.send(msgid: Int32(msgId.rawValue), data: data, expectsReply: false)
|
||||
.asCompletable()
|
||||
}
|
||||
|
||||
private func forceExitNvimServer() {
|
||||
@ -363,47 +374,23 @@ class UiBridge {
|
||||
self.nvimServerProc?.terminate()
|
||||
}
|
||||
|
||||
private func launchNvimUsingLoginShell() {
|
||||
let selfEnv = ProcessInfo.processInfo.environment
|
||||
|
||||
let shellPath = URL(fileURLWithPath: selfEnv["SHELL"] ?? "/bin/bash")
|
||||
let shellName = shellPath.lastPathComponent
|
||||
var shellArgs = [String]()
|
||||
if shellName != "tcsh" {
|
||||
// tcsh does not like the -l option
|
||||
shellArgs.append("-l")
|
||||
}
|
||||
if self.useInteractiveZsh && shellName == "zsh" {
|
||||
shellArgs.append("-i")
|
||||
}
|
||||
|
||||
private func launchNvimUsingLoginShellEnv() {
|
||||
let listenAddress = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("vimr_\(self.uuid).sock")
|
||||
var env = selfEnv
|
||||
var env = self.envDict
|
||||
env["NVIM_LISTEN_ADDRESS"] = listenAddress.path
|
||||
|
||||
let inputPipe = Pipe()
|
||||
let outPipe = Pipe()
|
||||
let errorPipe = Pipe()
|
||||
let process = Process()
|
||||
process.environment = env
|
||||
process.standardInput = inputPipe
|
||||
process.standardError = errorPipe
|
||||
process.standardOutput = outPipe
|
||||
process.currentDirectoryPath = self.cwd.path
|
||||
process.launchPath = shellPath.path
|
||||
process.arguments = shellArgs
|
||||
process.launchPath = self.nvimServerExecutablePath()
|
||||
process.arguments = [self.localServerName, self.remoteServerName] + ["--headless"] + self.nvimArgs
|
||||
process.launch()
|
||||
|
||||
self.nvimServerProc = process
|
||||
|
||||
nvimArgs.append("--headless")
|
||||
let cmd = "exec '\(self.nvimServerExecutablePath())' '\(self.localServerName)' '\(self.remoteServerName)' "
|
||||
.appending(self.nvimArgs.map { "'\($0)'" }.joined(separator: " "))
|
||||
|
||||
self.logger.debug(cmd)
|
||||
|
||||
let writeHandle = inputPipe.fileHandleForWriting
|
||||
guard let cmdData = cmd.data(using: .utf8) else {
|
||||
preconditionFailure("Could not get Data from the string '\(cmd)'")
|
||||
}
|
||||
writeHandle.write(cmdData)
|
||||
writeHandle.closeFile()
|
||||
}
|
||||
|
||||
private func nvimServerExecutablePath() -> String {
|
||||
@ -420,7 +407,8 @@ class UiBridge {
|
||||
|
||||
private let useInteractiveZsh: Bool
|
||||
private let cwd: URL
|
||||
private var nvimArgs: [String]
|
||||
private let nvimArgs: [String]
|
||||
private let envDict: [String: String]
|
||||
|
||||
private let server = RxMessagePortServer()
|
||||
private let client = RxMessagePortClient()
|
||||
|
@ -12,7 +12,8 @@
|
||||
1929B05B9D664052EC2D23EF /* FileOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BCE3E156C06EDF1F2806 /* FileOutlineView.swift */; };
|
||||
1929B08C6230B9C5AB72DAF1 /* Pref128ToCurrentConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B5046239709E33516F5C /* Pref128ToCurrentConverter.swift */; };
|
||||
1929B0E0C3BC59F52713D5A2 /* FoundationCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B9AF20D7BD6E5C975128 /* FoundationCommons.swift */; };
|
||||
1929B0F599D1F62C7BE53D2C /* HttpServerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1DC584C89C477E83FA2 /* HttpServerService.swift */; };
|
||||
1929B0F599D1F62C7BE53D2C /* HttpServerMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1DC584C89C477E83FA2 /* HttpServerMiddleware.swift */; };
|
||||
1929B10EE7FE8DC251B741B2 /* RxRedux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B66A5E2D00EA143AFD86 /* RxRedux.swift */; };
|
||||
1929B1837C750CADB3A5BCB9 /* OpenQuicklyFileViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1558455B3A74D93EF2A /* OpenQuicklyFileViewRow.swift */; };
|
||||
1929B20CE35B43BB1CE023BA /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BC2F05E9A5C0DB039739 /* Theme.swift */; };
|
||||
1929B29B95AD176D57942E08 /* UiRootReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B457B9D0FA4D21F3751E /* UiRootReducer.swift */; };
|
||||
@ -36,7 +37,7 @@
|
||||
1929B5543B1E31A26096E656 /* FileMonitorReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B04EC69F616EEFAF5F96 /* FileMonitorReducer.swift */; };
|
||||
1929B59FA5C286E010F70BEE /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BFC0A5A9C6DB09BE1368 /* Types.swift */; };
|
||||
1929B5A2EE366F79ED32744C /* KeysPrefReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B88B5FA08E897A3C2168 /* KeysPrefReducer.swift */; };
|
||||
1929B5C1BABBC0D09D97C3EF /* PreviewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B617C229B19DB3E987B8 /* PreviewService.swift */; };
|
||||
1929B5C1BABBC0D09D97C3EF /* PreviewMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B617C229B19DB3E987B8 /* PreviewMiddleware.swift */; };
|
||||
1929B5F016431A76292D1E84 /* FileMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B365A6434354B568B04F /* FileMonitor.swift */; };
|
||||
1929B6388EAF16C190B82955 /* FileItemIgnorePattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B69499B2569793350CEC /* FileItemIgnorePattern.swift */; };
|
||||
1929B6460862447A31B5B082 /* ImageAndTextTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BDC3F82CB4CB4FE56D1B /* ImageAndTextTableCell.swift */; };
|
||||
@ -54,13 +55,14 @@
|
||||
1929B9318D32146D58BB38EC /* AppKitCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A70931D60E04200E12030 /* AppKitCommons.swift */; };
|
||||
1929B94083273D4B321AD848 /* FileItemUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B56C8ED31834BA9D8543 /* FileItemUtils.swift */; };
|
||||
1929B98F94536E3912AD9F3B /* ArrayCommonsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BAF13FAD5DA8D3762367 /* ArrayCommonsTest.swift */; };
|
||||
1929B990A143763A56CFCED0 /* PrefService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B364460D86F17E80943C /* PrefService.swift */; };
|
||||
1929B990A143763A56CFCED0 /* PrefMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B364460D86F17E80943C /* PrefMiddleware.swift */; };
|
||||
1929BA715337FE26155B2071 /* BufferList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BA43449BA41666CD55ED /* BufferList.swift */; };
|
||||
1929BA76A1D97D8226F7CFB1 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B6AD3396160AA2C46919 /* Debouncer.swift */; };
|
||||
1929BAAD7336FDFF1F78E749 /* ScorerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BF69B01107F358CF7EAD /* ScorerTest.swift */; };
|
||||
1929BAE4900D72A7877741B1 /* PrefWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BE168F31344B69E61B62 /* PrefWindow.swift */; };
|
||||
1929BAFF1E011321D3186EE6 /* UiRoot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD4149D5A25C82064DD8 /* UiRoot.swift */; };
|
||||
1929BB4A9B2FA42A64CCCC76 /* MainWindowReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD83A13BF133741766CC /* MainWindowReducer.swift */; };
|
||||
1929BB67CAAD4F6CBD38DF0A /* RxRedux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B66A5E2D00EA143AFD86 /* RxRedux.swift */; };
|
||||
1929BBE28654E4307AF1E2FD /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BC2F05E9A5C0DB039739 /* Theme.swift */; };
|
||||
1929BCC7908DD899999B70BE /* AppearancePrefReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BED01F5D94BFCA4CF80F /* AppearancePrefReducer.swift */; };
|
||||
1929BCC9D3604933DFF07E2E /* FileBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BA5C7099CDEB04B76BA4 /* FileBrowser.swift */; };
|
||||
@ -278,16 +280,17 @@
|
||||
1929B12CE56A9B36980288A4 /* OpenQuicklyReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenQuicklyReducer.swift; sourceTree = "<group>"; };
|
||||
1929B14A5949FB64C4B2646F /* KeysPref.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeysPref.swift; sourceTree = "<group>"; };
|
||||
1929B1558455B3A74D93EF2A /* OpenQuicklyFileViewRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenQuicklyFileViewRow.swift; sourceTree = "<group>"; };
|
||||
1929B1DC584C89C477E83FA2 /* HttpServerService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServerService.swift; sourceTree = "<group>"; };
|
||||
1929B1DC584C89C477E83FA2 /* HttpServerMiddleware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServerMiddleware.swift; sourceTree = "<group>"; };
|
||||
1929B34FC23D805A8B29E8F7 /* Context.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = "<group>"; };
|
||||
1929B364460D86F17E80943C /* PrefService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefService.swift; sourceTree = "<group>"; };
|
||||
1929B364460D86F17E80943C /* PrefMiddleware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefMiddleware.swift; sourceTree = "<group>"; };
|
||||
1929B365A6434354B568B04F /* FileMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileMonitor.swift; sourceTree = "<group>"; };
|
||||
1929B457B9D0FA4D21F3751E /* UiRootReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UiRootReducer.swift; sourceTree = "<group>"; };
|
||||
1929B49E6924847AD085C8C9 /* PrefWindowReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefWindowReducer.swift; sourceTree = "<group>"; };
|
||||
1929B5046239709E33516F5C /* Pref128ToCurrentConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pref128ToCurrentConverter.swift; sourceTree = "<group>"; };
|
||||
1929B56C8ED31834BA9D8543 /* FileItemUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemUtils.swift; sourceTree = "<group>"; };
|
||||
1929B5D45C9792BBE76B8AFF /* StringCommonsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringCommonsTest.swift; sourceTree = "<group>"; };
|
||||
1929B617C229B19DB3E987B8 /* PreviewService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewService.swift; sourceTree = "<group>"; };
|
||||
1929B617C229B19DB3E987B8 /* PreviewMiddleware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewMiddleware.swift; sourceTree = "<group>"; };
|
||||
1929B66A5E2D00EA143AFD86 /* RxRedux.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxRedux.swift; sourceTree = "<group>"; };
|
||||
1929B67A10E6BB2986B2416E /* BufferListReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BufferListReducer.swift; sourceTree = "<group>"; };
|
||||
1929B694508FB5FDE607513A /* ToolsPrefReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolsPrefReducer.swift; sourceTree = "<group>"; };
|
||||
1929B69499B2569793350CEC /* FileItemIgnorePattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemIgnorePattern.swift; sourceTree = "<group>"; };
|
||||
@ -565,7 +568,7 @@
|
||||
1929B34FC23D805A8B29E8F7 /* Context.swift */,
|
||||
1929B32401E8914DE9BF76CA /* Components */,
|
||||
1929B5E773BDB3B4EE9D00C1 /* Reducers */,
|
||||
1929BFA93DC859DD76C46192 /* Services */,
|
||||
1929BFA93DC859DD76C46192 /* Middlewares */,
|
||||
1929BFC0A5A9C6DB09BE1368 /* Types.swift */,
|
||||
1929BA42AB6F1BF631B57399 /* SerializableStates.swift */,
|
||||
);
|
||||
@ -621,14 +624,14 @@
|
||||
name = "Open Quickly";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1929BFA93DC859DD76C46192 /* Services */ = {
|
||||
1929BFA93DC859DD76C46192 /* Middlewares */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1929B617C229B19DB3E987B8 /* PreviewService.swift */,
|
||||
1929B1DC584C89C477E83FA2 /* HttpServerService.swift */,
|
||||
1929B364460D86F17E80943C /* PrefService.swift */,
|
||||
1929B617C229B19DB3E987B8 /* PreviewMiddleware.swift */,
|
||||
1929B1DC584C89C477E83FA2 /* HttpServerMiddleware.swift */,
|
||||
1929B364460D86F17E80943C /* PrefMiddleware.swift */,
|
||||
);
|
||||
name = Services;
|
||||
name = Middlewares;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B5012001EBA791000F76C46 /* Frameworks */ = {
|
||||
@ -807,6 +810,7 @@
|
||||
4B6423941D8EFD6100FC78C8 /* Workspace */,
|
||||
4B97E2CF1D33F92200FC0660 /* resources */,
|
||||
1929BA652D3B88FC071531EC /* UI */,
|
||||
1929B66A5E2D00EA143AFD86 /* RxRedux.swift */,
|
||||
);
|
||||
path = VimR;
|
||||
sourceTree = "<group>";
|
||||
@ -1059,8 +1063,8 @@
|
||||
4BE45C1D1FD2DBD2005C0A95 /* Logger.swift in Sources */,
|
||||
1929B8FB248D71BF88A35761 /* PreviewTool.swift in Sources */,
|
||||
1929B4B70926DE113E6BF990 /* PreviewReducer.swift in Sources */,
|
||||
1929B5C1BABBC0D09D97C3EF /* PreviewService.swift in Sources */,
|
||||
1929B0F599D1F62C7BE53D2C /* HttpServerService.swift in Sources */,
|
||||
1929B5C1BABBC0D09D97C3EF /* PreviewMiddleware.swift in Sources */,
|
||||
1929B0F599D1F62C7BE53D2C /* HttpServerMiddleware.swift in Sources */,
|
||||
1929B3AC66EFE35D68C020E3 /* PreviewToolReducer.swift in Sources */,
|
||||
1929B59FA5C286E010F70BEE /* Types.swift in Sources */,
|
||||
1929B6D8F5FC723B7109031F /* OpenQuicklyReducer.swift in Sources */,
|
||||
@ -1083,7 +1087,7 @@
|
||||
1929B3557317755A43513B17 /* OpenQuicklyWindow.swift in Sources */,
|
||||
1929B333855A5406C400DA92 /* OpenQuicklyFilterOperation.swift in Sources */,
|
||||
1929B1837C750CADB3A5BCB9 /* OpenQuicklyFileViewRow.swift in Sources */,
|
||||
1929B990A143763A56CFCED0 /* PrefService.swift in Sources */,
|
||||
1929B990A143763A56CFCED0 /* PrefMiddleware.swift in Sources */,
|
||||
1929BA76A1D97D8226F7CFB1 /* Debouncer.swift in Sources */,
|
||||
1929B71610FF1DC6E459BA49 /* PreviewUtils.swift in Sources */,
|
||||
1929B08C6230B9C5AB72DAF1 /* Pref128ToCurrentConverter.swift in Sources */,
|
||||
@ -1103,6 +1107,7 @@
|
||||
1929BE0F64A6CE5BCE2A5092 /* MainWindow+Delegates.swift in Sources */,
|
||||
1929B8F498D1E7C53F572CE2 /* KeysPref.swift in Sources */,
|
||||
1929B5A2EE366F79ED32744C /* KeysPrefReducer.swift in Sources */,
|
||||
1929BB67CAAD4F6CBD38DF0A /* RxRedux.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1130,6 +1135,7 @@
|
||||
1929B8E90A1378E494D481E7 /* PrefUtilsTest.swift in Sources */,
|
||||
1929B20CE35B43BB1CE023BA /* Theme.swift in Sources */,
|
||||
1929B9318D32146D58BB38EC /* AppKitCommons.swift in Sources */,
|
||||
1929B10EE7FE8DC251B741B2 /* RxRedux.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1224,7 +1230,7 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 282;
|
||||
CURRENT_PROJECT_VERSION = 284;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@ -1281,7 +1287,7 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 282;
|
||||
CURRENT_PROJECT_VERSION = 284;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
|
@ -5,11 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class AdvancedPrefReducer {
|
||||
class AdvancedPrefReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, AdvancedPref.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = AdvancedPref.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
|
||||
switch pair.action {
|
||||
@ -29,6 +30,6 @@ class AdvancedPrefReducer {
|
||||
state.useSnapshotUpdate = value
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -12,10 +12,21 @@ import CocoaFontAwesome
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate {
|
||||
|
||||
struct OpenConfig {
|
||||
|
||||
var urls: [URL]
|
||||
var cwd: URL
|
||||
|
||||
var cliPipePath: String?
|
||||
var nvimArgs: [String]?
|
||||
var envDict: [String: String]?
|
||||
var line: Int?
|
||||
}
|
||||
|
||||
enum Action {
|
||||
|
||||
case newMainWindow(urls: [URL], cwd: URL, nvimArgs: [String]?, cliPipePath: String?)
|
||||
case openInKeyWindow(urls: [URL], cwd: URL, cliPipePath: String?)
|
||||
case newMainWindow(config: OpenConfig)
|
||||
case openInKeyWindow(config: OpenConfig)
|
||||
|
||||
case preferences
|
||||
}
|
||||
@ -24,10 +35,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
let baseServerUrl = URL(string: "http://localhost:\(NetUtils.openPort())")!
|
||||
|
||||
var initialAppState: AppState
|
||||
if let stateDict = UserDefaults.standard.value(forKey: PrefService.compatibleVersion) as? [String: Any] {
|
||||
if let stateDict = UserDefaults.standard.value(forKey: PrefMiddleware.compatibleVersion) as? [String: Any] {
|
||||
initialAppState = AppState(dict: stateDict) ?? .default
|
||||
} else {
|
||||
if let oldDict = UserDefaults.standard.value(forKey: PrefService.lastCompatibleVersion) as? [String: Any] {
|
||||
if let oldDict = UserDefaults.standard.value(forKey: PrefMiddleware.lastCompatibleVersion) as? [String: Any] {
|
||||
initialAppState = Pref128ToCurrentConverter.appState(from: oldDict)
|
||||
} else {
|
||||
initialAppState = .default
|
||||
@ -37,15 +48,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
baseServerUrl.appendingPathComponent(HtmlPreviewToolReducer.selectFirstPath)
|
||||
)
|
||||
|
||||
self.stateContext = Context(baseServerUrl: baseServerUrl, state: initialAppState)
|
||||
self.emit = self.stateContext.actionEmitter.typedEmit()
|
||||
self.context = Context(baseServerUrl: baseServerUrl, state: initialAppState)
|
||||
self.emit = self.context.actionEmitter.typedEmit()
|
||||
|
||||
self.openNewMainWindowOnLaunch = initialAppState.openNewMainWindowOnLaunch
|
||||
self.openNewMainWindowOnReactivation = initialAppState.openNewMainWindowOnReactivation
|
||||
self.useSnapshot = initialAppState.useSnapshotUpdate
|
||||
|
||||
let source = self.stateContext.stateSource
|
||||
self.uiRoot = UiRoot(source: source, emitter: self.stateContext.actionEmitter, state: initialAppState)
|
||||
let source = self.context.stateSource
|
||||
self.uiRoot = UiRoot(source: source, emitter: self.context.actionEmitter, state: initialAppState)
|
||||
|
||||
super.init()
|
||||
|
||||
@ -90,7 +101,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
}
|
||||
}
|
||||
|
||||
private let stateContext: Context
|
||||
private let context: Context
|
||||
private let emit: (Action) -> Void
|
||||
|
||||
private let uiRoot: UiRoot
|
||||
@ -157,7 +168,7 @@ extension AppDelegate {
|
||||
}
|
||||
|
||||
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
||||
self.stateContext.savePrefs()
|
||||
self.context.savePrefs()
|
||||
|
||||
if self.hasDirtyWindows && self.hasMainWindows {
|
||||
let alert = NSAlert()
|
||||
@ -188,7 +199,10 @@ extension AppDelegate {
|
||||
// For drag & dropping files on the App icon.
|
||||
func application(_ sender: NSApplication, openFiles filenames: [String]) {
|
||||
let urls = filenames.map { URL(fileURLWithPath: $0) }
|
||||
self.emit(.newMainWindow(urls: urls, cwd: FileUtils.userHomeUrl, nvimArgs: nil, cliPipePath: nil))
|
||||
let config = OpenConfig(
|
||||
urls: urls, cwd: FileUtils.userHomeUrl, cliPipePath: nil, nvimArgs: nil, envDict: nil, line: nil
|
||||
)
|
||||
self.emit(.newMainWindow(config: config))
|
||||
|
||||
sender.reply(toOpenOrPrint: .success)
|
||||
}
|
||||
@ -242,6 +256,21 @@ extension AppDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
let envDict: [String: String]?
|
||||
if let envPath = queryParam(envPathPrefix, from: rawParams, transforming: identity).first {
|
||||
envDict = stringDict(from: URL(fileURLWithPath: envPath))
|
||||
if FileManager.default.fileExists(atPath: envPath) {
|
||||
do {
|
||||
try FileManager.default.removeItem(atPath: envPath)
|
||||
} catch {
|
||||
fileLog.error(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
envDict = nil
|
||||
}
|
||||
|
||||
let line = queryParam(linePrefix, from: rawParams, transforming: { Int($0) }).compactMap { $0 }.first
|
||||
let urls = queryParam(filePrefix, from: rawParams, transforming: { URL(fileURLWithPath: $0) })
|
||||
let cwd = queryParam(cwdPrefix,
|
||||
from: rawParams,
|
||||
@ -258,26 +287,48 @@ extension AppDelegate {
|
||||
switch action {
|
||||
|
||||
case .activate, .newWindow:
|
||||
self.emit(.newMainWindow(urls: urls, cwd: cwd, nvimArgs: nil, cliPipePath: pipePath))
|
||||
let config = OpenConfig(urls: urls, cwd: cwd, cliPipePath: pipePath, nvimArgs: nil, envDict: envDict, line: line)
|
||||
self.emit(.newMainWindow(config: config))
|
||||
|
||||
case .open:
|
||||
self.emit(.openInKeyWindow(urls: urls, cwd: cwd, cliPipePath: pipePath))
|
||||
let config = OpenConfig(urls: urls, cwd: cwd, cliPipePath: pipePath, nvimArgs: nil, envDict: envDict, line: line)
|
||||
self.emit(.openInKeyWindow(config: config))
|
||||
|
||||
case .separateWindows:
|
||||
urls.forEach { self.emit(.newMainWindow(urls: [$0], cwd: cwd, nvimArgs: nil, cliPipePath: pipePath)) }
|
||||
urls.forEach {
|
||||
let config = OpenConfig(urls: [$0], cwd: cwd, cliPipePath: pipePath, nvimArgs: nil, envDict: nil, line: line)
|
||||
self.emit(.newMainWindow(config: config))
|
||||
}
|
||||
|
||||
case .nvim:
|
||||
self.emit(.newMainWindow(urls: [],
|
||||
cwd: cwd,
|
||||
nvimArgs: queryParam(nvimArgsPrefix, from: rawParams, transforming: identity),
|
||||
cliPipePath: pipePath))
|
||||
let config = OpenConfig(urls: urls,
|
||||
cwd: cwd,
|
||||
cliPipePath: pipePath,
|
||||
nvimArgs: queryParam(nvimArgsPrefix, from: rawParams, transforming: identity),
|
||||
envDict: envDict,
|
||||
line: line)
|
||||
self.emit(.newMainWindow(config: config))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private func stringDict(from jsonUrl: URL) -> [String: String]? {
|
||||
guard let data = try? Data(contentsOf: jsonUrl) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
return try JSONSerialization.jsonObject(with: data) as? [String: String]
|
||||
} catch {
|
||||
fileLog.error(error.localizedDescription)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func queryParam<T>(_ prefix: String,
|
||||
from rawParams: [String],
|
||||
transforming transform: (String) -> T) -> [T] {
|
||||
from rawParams: [String],
|
||||
transforming transform: (String) -> T) -> [T] {
|
||||
|
||||
return rawParams
|
||||
.filter { $0.hasPrefix(prefix) }
|
||||
@ -294,7 +345,10 @@ extension AppDelegate {
|
||||
}
|
||||
|
||||
@IBAction func newDocument(_ sender: Any?) {
|
||||
self.emit(.newMainWindow(urls: [], cwd: FileUtils.userHomeUrl, nvimArgs: nil, cliPipePath: nil))
|
||||
let config = OpenConfig(
|
||||
urls: [], cwd: FileUtils.userHomeUrl, cliPipePath: nil, nvimArgs: nil, envDict: nil, line: nil
|
||||
)
|
||||
self.emit(.newMainWindow(config: config))
|
||||
}
|
||||
|
||||
@IBAction func openInNewWindow(_ sender: Any?) {
|
||||
@ -318,7 +372,10 @@ extension AppDelegate {
|
||||
let urls = panel.urls
|
||||
let commonParentUrl = FileUtils.commonParent(of: urls)
|
||||
|
||||
self.emit(.newMainWindow(urls: urls, cwd: commonParentUrl, nvimArgs: nil, cliPipePath: nil))
|
||||
let config = OpenConfig(
|
||||
urls: urls, cwd: commonParentUrl, cliPipePath: nil, nvimArgs: nil, envDict: nil, line: nil
|
||||
)
|
||||
self.emit(.newMainWindow(config: config))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -331,7 +388,7 @@ extension AppDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep the rawValues in sync with Action in the `vimr` Python script.
|
||||
// Keep the rawValues in sync with Action in the `vimr` Python script.
|
||||
private enum VimRUrlAction: String {
|
||||
case activate = "activate"
|
||||
case open = "open"
|
||||
@ -344,8 +401,11 @@ private let updater = SUUpdater()
|
||||
|
||||
private let debugMenuItemIdentifier = NSUserInterfaceItemIdentifier("debug-menu-item")
|
||||
|
||||
// Keep in sync with QueryParamKey in the `vimr` Python script.
|
||||
private let filePrefix = "file="
|
||||
private let cwdPrefix = "cwd="
|
||||
private let nvimArgsPrefix = "nvim-args="
|
||||
private let pipePathPrefix = "pipe-path="
|
||||
private let waitPrefix = "wait="
|
||||
private let envPathPrefix = "env-path="
|
||||
private let linePrefix = "line="
|
||||
|
@ -5,59 +5,53 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class AppDelegateReducer {
|
||||
class AppDelegateReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, AppDelegate.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = AppDelegate.Action
|
||||
|
||||
init(baseServerUrl: URL) {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
}
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
|
||||
switch pair.action {
|
||||
|
||||
case let .newMainWindow(urls, cwd, nvimArgs, cliPipePath):
|
||||
let mainWindow: MainWindow.State
|
||||
if let args = nvimArgs {
|
||||
mainWindow = self.newMainWindow(with: state, urls: [], cwd: cwd, nvimArgs: args, cliPipePath: cliPipePath)
|
||||
} else {
|
||||
mainWindow = self.newMainWindow(with: state, urls: urls, cwd: cwd, cliPipePath: cliPipePath)
|
||||
}
|
||||
|
||||
case let .newMainWindow(config):
|
||||
let mainWindow = self.newMainWindow(with: state, config: config)
|
||||
state.mainWindows[mainWindow.uuid] = mainWindow
|
||||
|
||||
case let .openInKeyWindow(urls, cwd, cliPipePath):
|
||||
case let .openInKeyWindow(config):
|
||||
guard let uuid = state.currentMainWindowUuid, state.mainWindows[uuid] != nil else {
|
||||
let mainWindow = self.newMainWindow(with: state, urls: urls, cwd: cwd, cliPipePath: cliPipePath)
|
||||
let mainWindow = self.newMainWindow(with: state, config: config)
|
||||
state.mainWindows[mainWindow.uuid] = mainWindow
|
||||
break
|
||||
}
|
||||
|
||||
state.mainWindows[uuid]?.urlsToOpen = urls.toDict { url in MainWindow.OpenMode.default }
|
||||
state.mainWindows[uuid]?.cwd = cwd
|
||||
state.mainWindows[uuid]?.urlsToOpen = config.urls.toDict { url in MainWindow.OpenMode.default }
|
||||
state.mainWindows[uuid]?.cwd = config.cwd
|
||||
if let line = config.line {
|
||||
state.mainWindows[uuid]?.goToLineFromCli = Marked(line)
|
||||
}
|
||||
|
||||
case .preferences:
|
||||
state.preferencesOpen = Marked(true)
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
|
||||
private func newMainWindow(with state: AppState,
|
||||
urls: [URL],
|
||||
cwd: URL,
|
||||
nvimArgs: [String]? = nil,
|
||||
cliPipePath: String? = nil) -> MainWindow.State {
|
||||
private func newMainWindow(with state: AppState, config: AppDelegate.OpenConfig) -> MainWindow.State {
|
||||
|
||||
var mainWindow = state.mainWindowTemplate
|
||||
|
||||
mainWindow.uuid = UUID().uuidString
|
||||
mainWindow.cwd = cwd
|
||||
mainWindow.cwd = config.cwd
|
||||
mainWindow.isDirty = false
|
||||
|
||||
mainWindow.htmlPreview = HtmlPreviewState(
|
||||
@ -66,11 +60,15 @@ class AppDelegateReducer {
|
||||
)
|
||||
mainWindow.preview.server = self.baseServerUrl.appendingPathComponent(MarkdownReducer.nonePath)
|
||||
|
||||
mainWindow.nvimArgs = nvimArgs
|
||||
mainWindow.cliPipePath = cliPipePath
|
||||
mainWindow.urlsToOpen = urls.toDict { _ in MainWindow.OpenMode.default }
|
||||
mainWindow.nvimArgs = config.nvimArgs
|
||||
mainWindow.cliPipePath = config.cliPipePath
|
||||
mainWindow.envDict = config.envDict
|
||||
mainWindow.urlsToOpen = config.urls.toDict { _ in MainWindow.OpenMode.default }
|
||||
mainWindow.frame = state.mainWindows.isEmpty ? state.mainWindowTemplate.frame
|
||||
: self.frame(relativeTo: state.mainWindowTemplate.frame)
|
||||
: self.frame(relativeTo: state.mainWindowTemplate.frame)
|
||||
if let line = config.line {
|
||||
mainWindow.goToLineFromCli = Marked(line)
|
||||
}
|
||||
|
||||
return mainWindow
|
||||
}
|
||||
|
@ -5,11 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class AppearancePrefReducer {
|
||||
class AppearancePrefReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, AppearancePref.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = AppearancePref.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
var appearance = state.mainWindowTemplate.appearance
|
||||
|
||||
@ -34,7 +35,7 @@ class AppearancePrefReducer {
|
||||
|
||||
self.modify(state: &state, with: appearance)
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
|
||||
private func modify(state: inout AppState, with appearance: AppearanceState) {
|
||||
|
@ -5,20 +5,21 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class BuffersListReducer {
|
||||
class BuffersListReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<UuidState<MainWindow.State>, BuffersList.Action>
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<BuffersList.Action>
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
var state = pair.state.payload
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch pair.action {
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .open(buffer):
|
||||
state.currentBufferToSet = buffer
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: pair.state.uuid, state: state), action: pair.action)
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -6,179 +6,120 @@
|
||||
import Foundation
|
||||
import RxSwift
|
||||
|
||||
class Context {
|
||||
typealias AnyAction = Any
|
||||
extension ReduxTypes {
|
||||
|
||||
let stateSource: Observable<AppState>
|
||||
let actionEmitter = ActionEmitter()
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = AnyAction
|
||||
}
|
||||
|
||||
class Context: ReduxContext {
|
||||
|
||||
// The following should only be used when Cmd-Q'ing
|
||||
func savePrefs() {
|
||||
self.prefService.applyPref(from: self.appState)
|
||||
self.prefMiddleware.applyPref(from: self.state)
|
||||
}
|
||||
|
||||
init(baseServerUrl: URL, state: AppState) {
|
||||
self.appState = state
|
||||
self.stateSource = self.stateSubject.asObservable()
|
||||
super.init(initialState: state)
|
||||
|
||||
let openQuicklyReducer = OpenQuicklyReducer()
|
||||
let previewMiddleware = PreviewMiddleware()
|
||||
let markdownReducer = MarkdownReducer(baseServerUrl: baseServerUrl)
|
||||
|
||||
let previewService = PreviewService()
|
||||
let httpService: HttpServerService = HttpServerService(port: baseServerUrl.port!)
|
||||
let httpMiddleware: HttpServerMiddleware = HttpServerMiddleware(port: baseServerUrl.port!)
|
||||
let uiRootReducer = UiRootReducer()
|
||||
let openQuicklyReducer = OpenQuicklyReducer()
|
||||
|
||||
// AppState
|
||||
Observable
|
||||
.of(
|
||||
self.actionSourceForAppState()
|
||||
.reduce(by: AppDelegateReducer(baseServerUrl: baseServerUrl).reduce)
|
||||
.filterMapPair(),
|
||||
self.actionSourceForAppState()
|
||||
.reduce(by: uiRootReducer.reduceMainWindow)
|
||||
.reduce(by: openQuicklyReducer.reduceMainWindow)
|
||||
.filter { $0.modified }
|
||||
.apply(self.prefService.applyMainWindow)
|
||||
.map { $0.state },
|
||||
self.actionSourceForAppState()
|
||||
.reduce(by: FileMonitorReducer().reduce)
|
||||
.filterMapPair(),
|
||||
self.actionSourceForAppState()
|
||||
.reduce(by: openQuicklyReducer.reduceOpenQuicklyWindow)
|
||||
.filterMapPair(),
|
||||
self.actionSourceForAppState()
|
||||
.reduce(by: uiRootReducer.reduceUiRoot)
|
||||
.filterMapPair()
|
||||
)
|
||||
.merge()
|
||||
self.actionEmitter.observable
|
||||
.map { (state: self.state, action: $0, modified: false) }
|
||||
.reduce(
|
||||
by: [
|
||||
AppDelegateReducer(baseServerUrl: baseServerUrl).reduce,
|
||||
uiRootReducer.mainWindow.reduce,
|
||||
openQuicklyReducer.mainWindow.reduce,
|
||||
FileMonitorReducer().reduce,
|
||||
openQuicklyReducer.reduce,
|
||||
uiRootReducer.reduce,
|
||||
|
||||
// Preferences
|
||||
PrefWindowReducer().reduce,
|
||||
GeneralPrefReducer().reduce,
|
||||
ToolsPrefReducer().reduce,
|
||||
AppearancePrefReducer().reduce,
|
||||
AdvancedPrefReducer().reduce,
|
||||
KeysPrefReducer().reduce,
|
||||
],
|
||||
middlewares: [
|
||||
self.prefMiddleware.mainWindow.apply,
|
||||
self.prefMiddleware.apply,
|
||||
])
|
||||
.filter { $0.modified }
|
||||
.subscribe(onNext: self.emitAppState)
|
||||
.disposed(by: self.disposeBag)
|
||||
|
||||
// MainWindow.State
|
||||
Observable
|
||||
.of(
|
||||
self.actionSourceForMainWindow()
|
||||
.reduce(by: MainWindowReducer().reduce)
|
||||
.reduce(by: markdownReducer.reduceMainWindow)
|
||||
.filter { $0.modified }
|
||||
.apply(previewService.applyMainWindow)
|
||||
.apply(httpService.applyMainWindow)
|
||||
.map { $0.state },
|
||||
self.actionSourceForMainWindow()
|
||||
.reduce(by: markdownReducer.reducePreviewTool)
|
||||
.reduce(by: PreviewToolReducer(baseServerUrl: baseServerUrl).reduce)
|
||||
.filter { $0.modified }
|
||||
.apply(previewService.applyPreviewTool)
|
||||
.map { $0.state },
|
||||
self.actionSourceForMainWindow()
|
||||
.reduce(by: HtmlPreviewToolReducer(baseServerUrl: baseServerUrl).reduce)
|
||||
.filter { $0.modified }
|
||||
.apply(httpService.applyHtmlPreview)
|
||||
.map { $0.state },
|
||||
self.actionSourceForMainWindow()
|
||||
.reduce(by: FileBrowserReducer().reduce)
|
||||
.filterMapPair(),
|
||||
self.actionSourceForMainWindow()
|
||||
.reduce(by: BuffersListReducer().reduce)
|
||||
.reduce(by: markdownReducer.reduceBufferList)
|
||||
.filter { $0.modified }
|
||||
.apply(previewService.applyBufferList)
|
||||
.map { $0.state }
|
||||
self.actionEmitter.observable
|
||||
.mapOmittingNil { action in
|
||||
guard let uuidAction = action as? UuidTagged else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let mainWindowState = self.state.mainWindows[uuidAction.uuid] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (mainWindowState, action, false)
|
||||
}
|
||||
.reduce(
|
||||
by: [
|
||||
MainWindowReducer().reduce,
|
||||
markdownReducer.mainWindow.reduce,
|
||||
markdownReducer.previewTool.reduce,
|
||||
PreviewToolReducer(baseServerUrl: baseServerUrl).reduce,
|
||||
HtmlPreviewToolReducer(baseServerUrl: baseServerUrl).reduce,
|
||||
FileBrowserReducer().reduce,
|
||||
BuffersListReducer().reduce,
|
||||
markdownReducer.buffersList.reduce,
|
||||
],
|
||||
middlewares: [
|
||||
previewMiddleware.mainWindow.apply,
|
||||
httpMiddleware.mainWindow.apply,
|
||||
previewMiddleware.previewTool.apply,
|
||||
httpMiddleware.htmlPreview.apply,
|
||||
previewMiddleware.buffersList.apply,
|
||||
]
|
||||
)
|
||||
.merge()
|
||||
.filter { $0.modified }
|
||||
.subscribe(onNext: self.emitAppState)
|
||||
.disposed(by: self.disposeBag)
|
||||
|
||||
// Preferences
|
||||
Observable
|
||||
.of(
|
||||
self.prefStateSource(by: PrefWindowReducer().reduce, prefService: prefService),
|
||||
self.prefStateSource(by: GeneralPrefReducer().reduce, prefService: prefService),
|
||||
self.prefStateSource(by: ToolsPrefReducer().reduce, prefService: prefService),
|
||||
self.prefStateSource(by: AppearancePrefReducer().reduce, prefService: prefService),
|
||||
self.prefStateSource(by: AdvancedPrefReducer().reduce, prefService: prefService),
|
||||
self.prefStateSource(by: KeysPrefReducer().reduce, prefService: prefService)
|
||||
)
|
||||
.merge()
|
||||
.subscribe(onNext: self.emitAppState)
|
||||
.disposed(by: self.disposeBag)
|
||||
|
||||
#if DEBUG
|
||||
// self.actionEmitter.observable.debug().subscribe().disposed(by: self.disposeBag)
|
||||
// stateSource.debug().subscribe().disposed(by: self.disposeBag)
|
||||
#endif
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.stateSubject.onCompleted()
|
||||
}
|
||||
private let prefMiddleware = PrefMiddleware()
|
||||
|
||||
private let stateSubject = PublishSubject<AppState>()
|
||||
private let scheduler = SerialDispatchQueueScheduler(qos: .userInitiated)
|
||||
private let disposeBag = DisposeBag()
|
||||
private func emitAppState(_ tuple: (state: MainWindow.State, action: AnyAction, modified: Bool)) {
|
||||
guard let uuidAction = tuple.action as? UuidTagged else {
|
||||
return
|
||||
}
|
||||
|
||||
private var appState: AppState
|
||||
|
||||
private let prefService = PrefService()
|
||||
|
||||
private func emitAppState(_ mainWindow: UuidState<MainWindow.State>) {
|
||||
self.appState.mainWindows[mainWindow.uuid] = mainWindow.payload
|
||||
self.stateSubject.onNext(self.appState)
|
||||
self.state.mainWindows[uuidAction.uuid] = tuple.state
|
||||
self.stateSubject.onNext(self.state)
|
||||
|
||||
self.cleanUpAppState()
|
||||
}
|
||||
|
||||
private func emitAppState(_ appState: AppState) {
|
||||
self.appState = appState
|
||||
self.stateSubject.onNext(self.appState)
|
||||
private func emitAppState(_ tuple: ReduxTypes.ReduceTuple) {
|
||||
self.state = tuple.state
|
||||
self.stateSubject.onNext(self.state)
|
||||
|
||||
self.cleanUpAppState()
|
||||
}
|
||||
|
||||
private func cleanUpAppState() {
|
||||
self.appState.mainWindows.keys.forEach { uuid in
|
||||
self.appState.mainWindows[uuid]?.cwdToSet = nil
|
||||
self.appState.mainWindows[uuid]?.currentBufferToSet = nil
|
||||
self.appState.mainWindows[uuid]?.viewToBeFocused = nil
|
||||
self.appState.mainWindows[uuid]?.urlsToOpen.removeAll()
|
||||
self.state.mainWindows.keys.forEach { uuid in
|
||||
self.state.mainWindows[uuid]?.cwdToSet = nil
|
||||
self.state.mainWindows[uuid]?.currentBufferToSet = nil
|
||||
self.state.mainWindows[uuid]?.viewToBeFocused = nil
|
||||
self.state.mainWindows[uuid]?.urlsToOpen.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
private func actionSourceForAppState<ActionType>() -> Observable<StateActionPair<AppState, ActionType>> {
|
||||
return self.actionEmitter.observable
|
||||
.mapOmittingNil { $0 as? ActionType }
|
||||
.map { self.appStateActionPair(for: $0) }
|
||||
}
|
||||
|
||||
private func actionSourceForMainWindow<ActionType>()
|
||||
-> Observable<StateActionPair<UuidState<MainWindow.State>, ActionType>> {
|
||||
return self.actionEmitter.observable
|
||||
.mapOmittingNil { $0 as? UuidAction<ActionType> }
|
||||
.mapOmittingNil { self.mainWindowStateActionPair(for: $0) }
|
||||
}
|
||||
|
||||
private func prefStateSource<ActionType>(
|
||||
by reduce: @escaping (StateActionPair<AppState, ActionType>) -> StateActionPair<AppState, ActionType>,
|
||||
prefService: PrefService
|
||||
) -> Observable<AppState> {
|
||||
return self.actionSourceForAppState()
|
||||
.reduce(by: reduce)
|
||||
.filter { $0.modified }
|
||||
.apply(self.prefService.applyPref)
|
||||
.map { $0.state }
|
||||
}
|
||||
|
||||
private func appStateActionPair<ActionType>(for action: ActionType) -> StateActionPair<AppState, ActionType> {
|
||||
return StateActionPair(state: self.appState, action: action, modified: false)
|
||||
}
|
||||
|
||||
private func mainWindowStateActionPair<ActionType>(for action: UuidAction<ActionType>)
|
||||
-> StateActionPair<UuidState<MainWindow.State>, ActionType>? {
|
||||
guard let mainWindowState = self.appState.mainWindows[action.uuid] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: action.uuid, state: mainWindowState),
|
||||
action: action.payload,
|
||||
modified: false)
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,15 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class FileBrowserReducer {
|
||||
class FileBrowserReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<UuidState<MainWindow.State>, FileBrowser.Action>
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<FileBrowser.Action>
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
var state = pair.state.payload
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch pair.action {
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .open(url, mode):
|
||||
state.urlsToOpen[url] = mode
|
||||
@ -29,6 +30,6 @@ class FileBrowserReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: state.uuid, state: state), action: pair.action)
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,15 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class FileMonitorReducer {
|
||||
class FileMonitorReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, FileMonitor.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = FileMonitor.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
var state = pair.state
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch pair.action {
|
||||
switch tuple.action {
|
||||
|
||||
case let .change(in: url):
|
||||
if let fileItem = FileItemUtils.item(for: url, root: state.openQuickly.root, create: false) {
|
||||
@ -28,6 +29,6 @@ class FileMonitorReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class GeneralPrefReducer {
|
||||
class GeneralPrefReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, GeneralPref.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = GeneralPref.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
|
||||
switch pair.action {
|
||||
@ -29,6 +30,6 @@ class GeneralPrefReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,10 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class HtmlPreviewToolReducer {
|
||||
class HtmlPreviewToolReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<UuidState<MainWindow.State>, HtmlPreviewTool.Action>
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<HtmlPreviewTool.Action>
|
||||
|
||||
static let basePath = "/tools/html-preview"
|
||||
static let selectFirstPath = "/tools/html-preview/select-first.html"
|
||||
@ -16,11 +17,11 @@ class HtmlPreviewToolReducer {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
}
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
var state = pair.state.payload
|
||||
let uuid = pair.state.uuid
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
let uuid = pair.action.uuid
|
||||
|
||||
switch pair.action {
|
||||
switch pair.action.payload {
|
||||
|
||||
case let .selectHtmlFile(url):
|
||||
state.htmlPreview.htmlFile = url
|
||||
@ -30,7 +31,7 @@ class HtmlPreviewToolReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: state.uuid, state: state), action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
|
121
VimR/VimR/HttpServerMiddleware.swift
Normal file
121
VimR/VimR/HttpServerMiddleware.swift
Normal file
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import Swifter
|
||||
|
||||
class HttpServerMiddleware {
|
||||
|
||||
let htmlPreview: HtmlPreviewMiddleware
|
||||
let mainWindow: MainWindowMiddleware
|
||||
|
||||
init(port: Int) {
|
||||
let server = HttpServer()
|
||||
let resourceUrl = Bundle.main.resourceURL!
|
||||
let githubCssUrl = resourceUrl.appendingPathComponent("markdown/github-markdown.css")
|
||||
|
||||
self.htmlPreview = HtmlPreviewMiddleware(server: server, githubCssUrl: githubCssUrl)
|
||||
self.mainWindow = MainWindowMiddleware(server: server, githubCssUrl: githubCssUrl)
|
||||
|
||||
do {
|
||||
try server.start(in_port_t(port))
|
||||
stdoutLog.info("VimR http server started on http://localhost:\(port)")
|
||||
|
||||
let previewResourceUrl = resourceUrl.appendingPathComponent("preview")
|
||||
|
||||
server["\(MarkdownReducer.basePath)/:path"] = shareFilesFromDirectory(previewResourceUrl.path)
|
||||
server.GET["\(MarkdownReducer.basePath)/github-markdown.css"] = shareFile(githubCssUrl.path)
|
||||
|
||||
server["\(HtmlPreviewToolReducer.basePath)/:path"] = shareFilesFromDirectory(previewResourceUrl.path)
|
||||
server.GET["\(HtmlPreviewToolReducer.basePath)/github-markdown.css"] = shareFile(githubCssUrl.path)
|
||||
} catch {
|
||||
stdoutLog.error("Server could not be started on port \(port): \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
class HtmlPreviewMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<HtmlPreviewTool.Action>
|
||||
|
||||
init(server: HttpServer, githubCssUrl: URL) {
|
||||
self.server = server
|
||||
self.githubCssUrl = githubCssUrl
|
||||
}
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
let state = result.state
|
||||
guard let serverUrl = state.htmlPreview.server, let htmlFileUrl = state.htmlPreview.htmlFile else {
|
||||
return result
|
||||
}
|
||||
|
||||
let basePath = serverUrl.payload.deletingLastPathComponent().path
|
||||
|
||||
self.server.GET[serverUrl.payload.path] = shareFile(htmlFileUrl.path)
|
||||
self.server["\(basePath)/:path"] = shareFilesFromDirectory(htmlFileUrl.parent.path)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private let server: HttpServer
|
||||
private let githubCssUrl: URL
|
||||
}
|
||||
|
||||
class MainWindowMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
init(server: HttpServer, githubCssUrl: URL) {
|
||||
self.server = server
|
||||
self.githubCssUrl = githubCssUrl
|
||||
}
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
let uuidAction = tuple.action
|
||||
guard case .newCurrentBuffer = uuidAction.payload else {
|
||||
return result
|
||||
}
|
||||
|
||||
let preview = result.state.preview
|
||||
guard case .markdown = preview.status,
|
||||
let buffer = preview.buffer,
|
||||
let html = preview.html,
|
||||
let server = preview.server else {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fileLog.debug("Serving \(html) on \(server)")
|
||||
|
||||
let htmlBasePath = server.deletingLastPathComponent().path
|
||||
|
||||
self.server["\(htmlBasePath)/:path"] = shareFilesFromDirectory(buffer.deletingLastPathComponent().path)
|
||||
self.server.GET[server.path] = shareFile(html.path)
|
||||
self.server.GET["\(htmlBasePath)/github-markdown.css"] = shareFile(self.githubCssUrl.path)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private let server: HttpServer
|
||||
private let githubCssUrl: URL
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import Swifter
|
||||
|
||||
class HttpServerService {
|
||||
|
||||
typealias HtmlPreviewPair = StateActionPair<UuidState<MainWindow.State>, HtmlPreviewTool.Action>
|
||||
typealias MainWindowPair = StateActionPair<UuidState<MainWindow.State>, MainWindow.Action>
|
||||
|
||||
init(port: Int) {
|
||||
let resourceUrl = Bundle.main.resourceURL!
|
||||
self.githubCssUrl = resourceUrl.appendingPathComponent("markdown/github-markdown.css")
|
||||
|
||||
do {
|
||||
try self.server.start(in_port_t(port))
|
||||
stdoutLog.info("VimR http server started on http://localhost:\(port)")
|
||||
|
||||
let previewResourceUrl = resourceUrl.appendingPathComponent("preview")
|
||||
|
||||
self.server["\(MarkdownReducer.basePath)/:path"] = shareFilesFromDirectory(previewResourceUrl.path)
|
||||
self.server.GET["\(MarkdownReducer.basePath)/github-markdown.css"] = shareFile(githubCssUrl.path)
|
||||
|
||||
self.server["\(HtmlPreviewToolReducer.basePath)/:path"] = shareFilesFromDirectory(previewResourceUrl.path)
|
||||
self.server.GET["\(HtmlPreviewToolReducer.basePath)/github-markdown.css"] = shareFile(githubCssUrl.path)
|
||||
} catch {
|
||||
NSLog("ERROR server could not be started on port \(port)")
|
||||
}
|
||||
}
|
||||
|
||||
func applyHtmlPreview(_ pair: HtmlPreviewPair) {
|
||||
let state = pair.state.payload
|
||||
|
||||
guard let serverUrl = state.htmlPreview.server, let htmlFileUrl = state.htmlPreview.htmlFile else {
|
||||
return
|
||||
}
|
||||
|
||||
let basePath = serverUrl.payload.deletingLastPathComponent().path
|
||||
|
||||
self.server.GET[serverUrl.payload.path] = shareFile(htmlFileUrl.path)
|
||||
self.server["\(basePath)/:path"] = shareFilesFromDirectory(htmlFileUrl.parent.path)
|
||||
}
|
||||
|
||||
func applyMainWindow(_ pair: MainWindowPair) {
|
||||
guard case .newCurrentBuffer = pair.action else {
|
||||
return
|
||||
}
|
||||
|
||||
let preview = pair.state.payload.preview
|
||||
guard case .markdown = preview.status,
|
||||
let buffer = preview.buffer,
|
||||
let html = preview.html,
|
||||
let server = preview.server
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
fileLog.debug("Serving \(html) on \(server)")
|
||||
|
||||
let htmlBasePath = server.deletingLastPathComponent().path
|
||||
|
||||
self.server["\(htmlBasePath)/:path"] = shareFilesFromDirectory(buffer.deletingLastPathComponent().path)
|
||||
self.server.GET[server.path] = shareFile(html.path)
|
||||
self.server.GET["\(htmlBasePath)/github-markdown.css"] = shareFile(self.githubCssUrl.path)
|
||||
}
|
||||
|
||||
private let server = HttpServer()
|
||||
private let githubCssUrl: URL
|
||||
}
|
@ -1224,7 +1224,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.24.0</string>
|
||||
<string>SNAPSHOT-284</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@ -1241,7 +1241,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>282</string>
|
||||
<string>284</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.productivity</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
@ -5,11 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class KeysPrefReducer {
|
||||
class KeysPrefReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, KeysPref.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = KeysPref.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
|
||||
switch pair.action {
|
||||
@ -24,6 +25,6 @@ class KeysPrefReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +112,8 @@ class MainWindow: NSObject,
|
||||
|
||||
let neoVimViewConfig = NvimView.Config(useInteractiveZsh: state.useInteractiveZsh,
|
||||
cwd: state.cwd,
|
||||
nvimArgs: state.nvimArgs)
|
||||
nvimArgs: state.nvimArgs,
|
||||
envDict: state.envDict)
|
||||
self.neoVimView = NvimView(frame: .zero, config: neoVimViewConfig)
|
||||
self.neoVimView.configureForAutoLayout()
|
||||
|
||||
@ -259,23 +260,39 @@ class MainWindow: NSObject,
|
||||
if let cwd = state.cwdToSet {
|
||||
self.neoVimView.cwd = cwd
|
||||
}
|
||||
if state.preview.status == .markdown
|
||||
&& state.previewTool.isReverseSearchAutomatically
|
||||
&& state.preview.previewPosition.hasDifferentMark(as: self.previewPosition) {
|
||||
self.neoVimView
|
||||
.cursorGo(to: state.preview.previewPosition.payload)
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
self.previewPosition = state.preview.previewPosition
|
||||
Completable
|
||||
.empty()
|
||||
.andThen {
|
||||
if state.preview.status == .markdown
|
||||
&& state.previewTool.isReverseSearchAutomatically
|
||||
&& state.preview.previewPosition.hasDifferentMark(as: self.previewPosition) {
|
||||
|
||||
self.open(urls: state.urlsToOpen)
|
||||
self.previewPosition = state.preview.previewPosition
|
||||
return self.neoVimView.cursorGo(to: state.preview.previewPosition.payload)
|
||||
}
|
||||
|
||||
if let currentBuffer = state.currentBufferToSet {
|
||||
self.neoVimView
|
||||
.select(buffer: currentBuffer)
|
||||
.subscribe()
|
||||
}
|
||||
return .empty()
|
||||
}
|
||||
.andThen(self.open(urls: state.urlsToOpen))
|
||||
.andThen {
|
||||
if let currentBuffer = state.currentBufferToSet {
|
||||
return self.neoVimView.select(buffer: currentBuffer)
|
||||
}
|
||||
|
||||
return .empty()
|
||||
}
|
||||
.andThen {
|
||||
if self.goToLineFromCli?.mark != state.goToLineFromCli?.mark {
|
||||
self.goToLineFromCli = state.goToLineFromCli
|
||||
if let goToLine = self.goToLineFromCli {
|
||||
return self.neoVimView.goTo(line: goToLine.payload)
|
||||
}
|
||||
}
|
||||
|
||||
return .empty()
|
||||
}
|
||||
.subscribe()
|
||||
|
||||
let usesTheme = state.appearance.usesTheme
|
||||
let themePrefChanged = state.appearance.usesTheme != self.usesTheme
|
||||
@ -335,7 +352,17 @@ class MainWindow: NSObject,
|
||||
self.window.setFrame(state.frame, display: true)
|
||||
self.window.makeFirstResponder(self.neoVimView)
|
||||
|
||||
self.open(urls: state.urlsToOpen)
|
||||
self.goToLineFromCli = state.goToLineFromCli
|
||||
self
|
||||
.open(urls: state.urlsToOpen)
|
||||
.andThen {
|
||||
if let goToLine = self.goToLineFromCli {
|
||||
return self.neoVimView.goTo(line: goToLine.payload)
|
||||
}
|
||||
|
||||
return .empty()
|
||||
}
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
func uuidAction(for action: Action) -> UuidAction<Action> {
|
||||
@ -364,6 +391,8 @@ class MainWindow: NSObject,
|
||||
|
||||
private var currentBuffer: NvimView.Buffer?
|
||||
|
||||
private var goToLineFromCli: Marked<Int>?
|
||||
|
||||
private var defaultFont = NvimView.defaultFont
|
||||
private var linespacing = NvimView.defaultLinespacing
|
||||
private var usesLigatures = false
|
||||
@ -402,26 +431,25 @@ class MainWindow: NSObject,
|
||||
self.workspace.theme = workspaceTheme
|
||||
}
|
||||
|
||||
private func open(urls: [URL: OpenMode]) {
|
||||
// If we don't call the following in the next tick, only half of the existing swap file warning is displayed.
|
||||
// Dunno why...
|
||||
DispatchQueue.main.async {
|
||||
Completable.concat(
|
||||
urls.map { entry -> Completable in
|
||||
let url = entry.key
|
||||
let mode = entry.value
|
||||
|
||||
switch mode {
|
||||
case .default: return self.neoVimView.open(urls: [url])
|
||||
case .currentTab: return self.neoVimView.openInCurrentTab(url: url)
|
||||
case .newTab: return self.neoVimView.openInNewTab(urls: [url])
|
||||
case .horizontalSplit: return self.neoVimView.openInHorizontalSplit(urls: [url])
|
||||
case .verticalSplit: return self.neoVimView.openInVerticalSplit(urls: [url])
|
||||
}
|
||||
}
|
||||
)
|
||||
.subscribe()
|
||||
private func open(urls: [URL: OpenMode]) -> Completable {
|
||||
if urls.isEmpty {
|
||||
return .empty()
|
||||
}
|
||||
|
||||
return .concat(
|
||||
urls.map { entry -> Completable in
|
||||
let url = entry.key
|
||||
let mode = entry.value
|
||||
|
||||
switch mode {
|
||||
case .default: return self.neoVimView.open(urls: [url])
|
||||
case .currentTab: return self.neoVimView.openInCurrentTab(url: url)
|
||||
case .newTab: return self.neoVimView.openInNewTab(urls: [url])
|
||||
case .horizontalSplit: return self.neoVimView.openInHorizontalSplit(urls: [url])
|
||||
case .verticalSplit: return self.neoVimView.openInVerticalSplit(urls: [url])
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func addViews() {
|
||||
|
@ -5,14 +5,15 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class MainWindowReducer {
|
||||
class MainWindowReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<UuidState<MainWindow.State>, MainWindow.Action>
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
var state = pair.state.payload
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch pair.action {
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .frameChanged(to:frame):
|
||||
state.frame = frame
|
||||
@ -30,8 +31,8 @@ class MainWindowReducer {
|
||||
|
||||
case let .setDirtyStatus(status):
|
||||
// When I gt or w around, we change tab somehow... Dunno why...
|
||||
if status == pair.state.payload.isDirty {
|
||||
return pair
|
||||
if status == tuple.state.isDirty {
|
||||
return tuple
|
||||
}
|
||||
|
||||
state.isDirty = status
|
||||
@ -78,10 +79,10 @@ class MainWindowReducer {
|
||||
state.appearance.theme = Marked(theme)
|
||||
|
||||
default:
|
||||
return pair
|
||||
return tuple
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: state.uuid, state: state), action: pair.action)
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,14 @@
|
||||
import Foundation
|
||||
import RxSwift
|
||||
|
||||
class OpenQuicklyReducer {
|
||||
class OpenQuicklyReducer: ReducerType {
|
||||
|
||||
typealias OpenQuicklyWindowPair = StateActionPair<AppState, OpenQuicklyWindow.Action>
|
||||
typealias MainWindowPair = StateActionPair<AppState, UuidAction<MainWindow.Action>>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = OpenQuicklyWindow.Action
|
||||
|
||||
func reduceOpenQuicklyWindow(_ pair: OpenQuicklyWindowPair) -> OpenQuicklyWindowPair {
|
||||
let mainWindow = MainWindowReducer()
|
||||
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var appState = pair.state
|
||||
|
||||
appState.openQuickly.open = false
|
||||
@ -32,31 +34,37 @@ class OpenQuicklyReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: appState, action: pair.action)
|
||||
return (appState, pair.action, true)
|
||||
}
|
||||
|
||||
func reduceMainWindow(_ pair: MainWindowPair) -> MainWindowPair {
|
||||
switch pair.action.payload {
|
||||
class MainWindowReducer: ReducerType {
|
||||
|
||||
case .openQuickly:
|
||||
var appState = pair.state
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
guard let uuid = appState.currentMainWindowUuid else {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
switch pair.action.payload {
|
||||
|
||||
case .openQuickly:
|
||||
var appState = pair.state
|
||||
|
||||
guard let uuid = appState.currentMainWindowUuid else {
|
||||
return pair
|
||||
}
|
||||
|
||||
guard let cwd = appState.mainWindows[uuid]?.cwd else {
|
||||
return pair
|
||||
}
|
||||
|
||||
appState.openQuickly.open = true
|
||||
appState.openQuickly.cwd = cwd
|
||||
|
||||
return (appState, pair.action, true)
|
||||
|
||||
default:
|
||||
return pair
|
||||
|
||||
}
|
||||
|
||||
guard let cwd = appState.mainWindows[uuid]?.cwd else {
|
||||
return pair
|
||||
}
|
||||
|
||||
appState.openQuickly.open = true
|
||||
appState.openQuickly.cwd = cwd
|
||||
|
||||
return StateActionPair(state: appState, action: pair.action)
|
||||
|
||||
default:
|
||||
return pair
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
62
VimR/VimR/PrefMiddleware.swift
Normal file
62
VimR/VimR/PrefMiddleware.swift
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
class PrefMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = AnyAction
|
||||
|
||||
static let compatibleVersion = "168"
|
||||
static let lastCompatibleVersion = "128"
|
||||
|
||||
let mainWindow = MainWindowMiddleware()
|
||||
|
||||
// The following should only be used when Cmd-Q'ing
|
||||
func applyPref(from appState: AppState) {
|
||||
defaults.setValue(appState.dict(), forKey: PrefMiddleware.compatibleVersion)
|
||||
}
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
defaults.setValue(result.state.dict(), forKey: PrefMiddleware.compatibleVersion)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class MainWindowMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
let uuidAction = tuple.action
|
||||
guard case .close = uuidAction.payload else {
|
||||
return result
|
||||
}
|
||||
|
||||
defaults.setValue(result.state.dict(), forKey: PrefMiddleware.compatibleVersion)
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let defaults = UserDefaults.standard
|
@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
private let defaults = UserDefaults.standard
|
||||
|
||||
class PrefService {
|
||||
|
||||
typealias MainWindowPair = StateActionPair<AppState, UuidAction<MainWindow.Action>>
|
||||
|
||||
static let compatibleVersion = "168"
|
||||
static let lastCompatibleVersion = "128"
|
||||
|
||||
// The following should only be used when Cmd-Q'ing
|
||||
func applyPref(from appState: AppState) {
|
||||
defaults.setValue(appState.dict(), forKey: PrefService.compatibleVersion)
|
||||
}
|
||||
|
||||
func applyPref<ActionType>(_ pair: StateActionPair<AppState, ActionType>) {
|
||||
defaults.setValue(pair.state.dict(), forKey: PrefService.compatibleVersion)
|
||||
}
|
||||
|
||||
func applyMainWindow(_ pair: MainWindowPair) {
|
||||
guard case .close = pair.action.payload else {
|
||||
return
|
||||
}
|
||||
|
||||
defaults.setValue(pair.state.dict(), forKey: PrefService.compatibleVersion)
|
||||
}
|
||||
}
|
@ -5,11 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class PrefWindowReducer {
|
||||
class PrefWindowReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, PrefWindow.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = PrefWindow.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
|
||||
switch pair.action {
|
||||
@ -19,6 +20,6 @@ class PrefWindowReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
}
|
||||
|
186
VimR/VimR/PreviewMiddleware.swift
Normal file
186
VimR/VimR/PreviewMiddleware.swift
Normal file
@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import CocoaMarkdown
|
||||
|
||||
class PreviewMiddleware {
|
||||
|
||||
let previewTool: PreviewToolMiddleware
|
||||
let buffersList: BuffersListMiddleware
|
||||
let mainWindow: MainWindowMiddleware
|
||||
|
||||
init() {
|
||||
let generator = PreviewGenerator()
|
||||
self.previewTool = PreviewToolMiddleware(generator: generator)
|
||||
self.buffersList = BuffersListMiddleware(generator: generator)
|
||||
self.mainWindow = MainWindowMiddleware(generator: generator)
|
||||
}
|
||||
|
||||
class PreviewGenerator {
|
||||
|
||||
init() {
|
||||
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
|
||||
}
|
||||
|
||||
func apply(_ state: MainWindow.State, uuid: String) {
|
||||
let preview = state.preview
|
||||
guard let buffer = preview.buffer, let html = preview.html else {
|
||||
guard let previewUrl = self.previewFiles[uuid] else {
|
||||
return
|
||||
}
|
||||
|
||||
try? FileManager.default.removeItem(at: previewUrl)
|
||||
self.previewFiles.removeValue(forKey: uuid)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
fileLog.debug("\(buffer) -> \(html)")
|
||||
do {
|
||||
try self.render(buffer, to: html)
|
||||
self.previewFiles[uuid] = html
|
||||
} catch let error as NSError {
|
||||
// FIXME: error handling!
|
||||
NSLog("ERROR rendering \(buffer) to \(html): \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func render(_ bufferUrl: URL, to htmlUrl: URL) throws {
|
||||
let doc = CMDocument(contentsOfFile: bufferUrl.path, options: .sourcepos)
|
||||
let renderer = CMHTMLRenderer(document: doc)
|
||||
|
||||
guard let body = renderer?.render() else {
|
||||
// FIXME: error handling!
|
||||
return
|
||||
}
|
||||
|
||||
let html = filledTemplate(body: body, title: bufferUrl.lastPathComponent)
|
||||
let htmlFilePath = htmlUrl.path
|
||||
|
||||
try html.write(toFile: htmlFilePath, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
private func filledTemplate(body: String, title: String) -> String {
|
||||
return self.template
|
||||
.replacingOccurrences(of: "{{ title }}", with: title)
|
||||
.replacingOccurrences(of: "{{ body }}", with: body)
|
||||
}
|
||||
|
||||
private let template: String
|
||||
private var previewFiles = [String: URL]()
|
||||
}
|
||||
|
||||
class PreviewToolMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<PreviewTool.Action>
|
||||
|
||||
init(generator: PreviewGenerator) {
|
||||
self.generator = generator
|
||||
}
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
let uuidAction = tuple.action
|
||||
guard case .refreshNow = uuidAction.payload else {
|
||||
return result
|
||||
}
|
||||
|
||||
self.generator.apply(result.state, uuid: uuidAction.uuid)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private let generator: PreviewGenerator
|
||||
}
|
||||
|
||||
class BuffersListMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<BuffersList.Action>
|
||||
|
||||
init(generator: PreviewGenerator) {
|
||||
self.generator = generator
|
||||
}
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
let uuidAction = tuple.action
|
||||
guard case .open = uuidAction.payload else {
|
||||
return result
|
||||
}
|
||||
|
||||
self.generator.apply(result.state, uuid: uuidAction.uuid)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private let generator: PreviewGenerator
|
||||
}
|
||||
|
||||
class MainWindowMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
init(generator: PreviewGenerator) {
|
||||
self.generator = generator
|
||||
}
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
let uuidAction = tuple.action
|
||||
switch uuidAction.payload {
|
||||
|
||||
case .newCurrentBuffer:
|
||||
self.generator.apply(result.state, uuid: uuidAction.uuid)
|
||||
|
||||
case .bufferWritten:
|
||||
self.generator.apply(result.state, uuid: uuidAction.uuid)
|
||||
|
||||
default:
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private let generator: PreviewGenerator
|
||||
}
|
||||
}
|
@ -7,124 +7,160 @@ import Foundation
|
||||
|
||||
class MarkdownReducer {
|
||||
|
||||
typealias PreviewToolPair = StateActionPair<UuidState<MainWindow.State>, PreviewTool.Action>
|
||||
typealias BufferListPair = StateActionPair<UuidState<MainWindow.State>, BuffersList.Action>
|
||||
typealias MainWindowPair = StateActionPair<UuidState<MainWindow.State>, MainWindow.Action>
|
||||
|
||||
static let basePath = "/tools/markdown"
|
||||
static let saveFirstPath = "/tools/markdown/save-first.html"
|
||||
static let errorPath = "/tools/markdown/error.html"
|
||||
static let nonePath = "/tools/markdown/empty.html"
|
||||
|
||||
func reducePreviewTool(_ pair: PreviewToolPair) -> PreviewToolPair {
|
||||
var state = pair.state.payload
|
||||
|
||||
switch pair.action {
|
||||
|
||||
case .refreshNow:
|
||||
state.preview = PreviewUtils.state(for: pair.state.uuid,
|
||||
baseUrl: self.baseServerUrl,
|
||||
buffer: state.currentBuffer,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .reload
|
||||
|
||||
case let .reverseSearch(to:position):
|
||||
state.preview.previewPosition = Marked(position)
|
||||
state.preview.lastSearch = .reverse
|
||||
|
||||
case let .scroll(to:position):
|
||||
if state.preview.lastSearch == .reload {
|
||||
state.preview.lastSearch = .none
|
||||
break;
|
||||
}
|
||||
|
||||
guard state.previewTool.isReverseSearchAutomatically && state.preview.lastSearch != .forward else {
|
||||
state.preview.lastSearch = .none
|
||||
state.preview.previewPosition = Marked(mark: state.preview.previewPosition.mark, payload: position)
|
||||
break;
|
||||
}
|
||||
|
||||
state.preview.previewPosition = Marked(position)
|
||||
state.preview.lastSearch = .reverse
|
||||
|
||||
default:
|
||||
return pair
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: state.uuid, state: state), action: pair.action)
|
||||
}
|
||||
|
||||
func reduceBufferList(_ pair: BufferListPair) -> BufferListPair {
|
||||
var state = pair.state.payload
|
||||
|
||||
switch pair.action {
|
||||
|
||||
case let .open(buffer):
|
||||
state.preview = PreviewUtils.state(for: pair.state.uuid,
|
||||
baseUrl: self.baseServerUrl,
|
||||
buffer: buffer,
|
||||
editorPosition: Marked(.beginning),
|
||||
previewPosition: Marked(.beginning))
|
||||
state.preview.lastSearch = .none
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: pair.state.uuid, state: state), action: pair.action)
|
||||
}
|
||||
|
||||
func reduceMainWindow(_ pair: MainWindowPair) -> MainWindowPair {
|
||||
var state = pair.state.payload
|
||||
|
||||
switch pair.action {
|
||||
|
||||
case let .newCurrentBuffer(buffer):
|
||||
state.preview = PreviewUtils.state(for: pair.state.uuid, baseUrl: self.baseServerUrl, buffer: buffer,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .none
|
||||
|
||||
case .bufferWritten:
|
||||
state.preview = PreviewUtils.state(for: pair.state.uuid,
|
||||
baseUrl: self.baseServerUrl,
|
||||
buffer: state.currentBuffer,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .reload
|
||||
|
||||
case let .setCursor(to:position):
|
||||
if state.preview.lastSearch == .reload {
|
||||
state.preview.lastSearch = .none
|
||||
break
|
||||
}
|
||||
|
||||
guard state.previewTool.isForwardSearchAutomatically && state.preview.lastSearch != .reverse else {
|
||||
state.preview.editorPosition = Marked(mark: state.preview.editorPosition.mark, payload: position.payload)
|
||||
state.preview.lastSearch = .none
|
||||
break
|
||||
}
|
||||
|
||||
state.preview.editorPosition = Marked(position.payload)
|
||||
state.preview.lastSearch = .none // .none because the forward search does not invoke .scroll above.
|
||||
|
||||
case .close:
|
||||
state.preview = PreviewUtils.state(for: .none,
|
||||
baseUrl: self.baseServerUrl,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .none
|
||||
|
||||
default:
|
||||
return pair
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: pair.state.uuid, state: state), action: pair.action)
|
||||
}
|
||||
let previewTool: PreviewToolReducer
|
||||
let buffersList: BuffersListReducer
|
||||
let mainWindow: MainWindowReducer
|
||||
|
||||
init(baseServerUrl: URL) {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
self.previewTool = PreviewToolReducer(baseServerUrl: baseServerUrl)
|
||||
self.buffersList = BuffersListReducer(baseServerUrl: baseServerUrl)
|
||||
self.mainWindow = MainWindowReducer(baseServerUrl: baseServerUrl)
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
class PreviewToolReducer: ReducerType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<PreviewTool.Action>
|
||||
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch tuple.action.payload {
|
||||
|
||||
case .refreshNow:
|
||||
state.preview = PreviewUtils.state(for: tuple.state.uuid,
|
||||
baseUrl: self.baseServerUrl,
|
||||
buffer: state.currentBuffer,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .reload
|
||||
|
||||
case let .reverseSearch(to:position):
|
||||
state.preview.previewPosition = Marked(position)
|
||||
state.preview.lastSearch = .reverse
|
||||
|
||||
case let .scroll(to:position):
|
||||
if state.preview.lastSearch == .reload {
|
||||
state.preview.lastSearch = .none
|
||||
break;
|
||||
}
|
||||
|
||||
guard state.previewTool.isReverseSearchAutomatically && state.preview.lastSearch != .forward else {
|
||||
state.preview.lastSearch = .none
|
||||
state.preview.previewPosition = Marked(mark: state.preview.previewPosition.mark, payload: position)
|
||||
break;
|
||||
}
|
||||
|
||||
state.preview.previewPosition = Marked(position)
|
||||
state.preview.lastSearch = .reverse
|
||||
|
||||
default:
|
||||
return tuple
|
||||
|
||||
}
|
||||
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
|
||||
init(baseServerUrl: URL) {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
}
|
||||
|
||||
class BuffersListReducer: ReducerType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<BuffersList.Action>
|
||||
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .open(buffer):
|
||||
state.preview = PreviewUtils.state(for: tuple.state.uuid,
|
||||
baseUrl: self.baseServerUrl,
|
||||
buffer: buffer,
|
||||
editorPosition: Marked(.beginning),
|
||||
previewPosition: Marked(.beginning))
|
||||
state.preview.lastSearch = .none
|
||||
|
||||
}
|
||||
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
|
||||
init(baseServerUrl: URL) {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
}
|
||||
|
||||
class MainWindowReducer: ReducerType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .newCurrentBuffer(buffer):
|
||||
state.preview = PreviewUtils.state(for: tuple.state.uuid, baseUrl: self.baseServerUrl, buffer: buffer,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .none
|
||||
|
||||
case .bufferWritten:
|
||||
state.preview = PreviewUtils.state(for: tuple.state.uuid,
|
||||
baseUrl: self.baseServerUrl,
|
||||
buffer: state.currentBuffer,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .reload
|
||||
|
||||
case let .setCursor(to:position):
|
||||
if state.preview.lastSearch == .reload {
|
||||
state.preview.lastSearch = .none
|
||||
break
|
||||
}
|
||||
|
||||
guard state.previewTool.isForwardSearchAutomatically && state.preview.lastSearch != .reverse else {
|
||||
state.preview.editorPosition = Marked(mark: state.preview.editorPosition.mark, payload: position.payload)
|
||||
state.preview.lastSearch = .none
|
||||
break
|
||||
}
|
||||
|
||||
state.preview.editorPosition = Marked(position.payload)
|
||||
state.preview.lastSearch = .none // .none because the forward search does not invoke .scroll above.
|
||||
|
||||
case .close:
|
||||
state.preview = PreviewUtils.state(for: .none,
|
||||
baseUrl: self.baseServerUrl,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .none
|
||||
|
||||
default:
|
||||
return tuple
|
||||
}
|
||||
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
|
||||
init(baseServerUrl: URL) {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
}
|
||||
}
|
||||
|
@ -1,104 +0,0 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import CocoaMarkdown
|
||||
|
||||
class PreviewService {
|
||||
|
||||
typealias PreviewToolPair = StateActionPair<UuidState<MainWindow.State>, PreviewTool.Action>
|
||||
typealias BufferListPair = StateActionPair<UuidState<MainWindow.State>, BuffersList.Action>
|
||||
typealias MainWindowPair = StateActionPair<UuidState<MainWindow.State>, MainWindow.Action>
|
||||
|
||||
init() {
|
||||
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
|
||||
}
|
||||
|
||||
func applyPreviewTool(_ pair: PreviewToolPair) {
|
||||
guard case .refreshNow = pair.action else {
|
||||
return
|
||||
}
|
||||
|
||||
self.apply(pair.state)
|
||||
}
|
||||
|
||||
func applyBufferList(_ pair: BufferListPair) {
|
||||
guard case .open = pair.action else {
|
||||
return
|
||||
}
|
||||
|
||||
self.apply(pair.state)
|
||||
}
|
||||
|
||||
func applyMainWindow(_ pair: MainWindowPair) {
|
||||
switch pair.action {
|
||||
case .newCurrentBuffer: self.apply(pair.state)
|
||||
case .bufferWritten: self.apply(pair.state)
|
||||
default: return
|
||||
}
|
||||
}
|
||||
|
||||
private func filledTemplate(body: String, title: String) -> String {
|
||||
return self.template
|
||||
.replacingOccurrences(of: "{{ title }}", with: title)
|
||||
.replacingOccurrences(of: "{{ body }}", with: body)
|
||||
}
|
||||
|
||||
private func render(_ bufferUrl: URL, to htmlUrl: URL) throws {
|
||||
let doc = CMDocument(contentsOfFile: bufferUrl.path, options: .sourcepos)
|
||||
let renderer = CMHTMLRenderer(document: doc)
|
||||
|
||||
guard let body = renderer?.render() else {
|
||||
// FIXME: error handling!
|
||||
return
|
||||
}
|
||||
|
||||
let html = filledTemplate(body: body, title: bufferUrl.lastPathComponent)
|
||||
let htmlFilePath = htmlUrl.path
|
||||
|
||||
try html.write(toFile: htmlFilePath, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
private func apply(_ state: UuidState<MainWindow.State>) {
|
||||
let uuid = state.uuid
|
||||
|
||||
let preview = state.payload.preview
|
||||
guard let buffer = preview.buffer, let html = preview.html else {
|
||||
guard let previewUrl = self.previewFiles[uuid] else {
|
||||
return
|
||||
}
|
||||
|
||||
try? FileManager.default.removeItem(at: previewUrl)
|
||||
self.previewFiles.removeValue(forKey: uuid)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NSLog("\(buffer) -> \(html)")
|
||||
do {
|
||||
try self.render(buffer, to: html)
|
||||
self.previewFiles[uuid] = html
|
||||
} catch let error as NSError {
|
||||
// FIXME: error handling!
|
||||
NSLog("ERROR rendering \(buffer) to \(html): \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private let template: String
|
||||
private let tempDir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||
private var previewFiles = [String: URL]()
|
||||
}
|
@ -5,18 +5,19 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class PreviewToolReducer {
|
||||
class PreviewToolReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<UuidState<MainWindow.State>, PreviewTool.Action>
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<PreviewTool.Action>
|
||||
|
||||
init(baseServerUrl: URL) {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
}
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
var state = pair.state.payload
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch pair.action {
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .setAutomaticReverseSearch(to:value):
|
||||
state.previewTool.isReverseSearchAutomatically = value
|
||||
@ -28,11 +29,11 @@ class PreviewToolReducer {
|
||||
state.previewTool.isRefreshOnWrite = value
|
||||
|
||||
default:
|
||||
return pair
|
||||
return tuple
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: state.uuid, state: state), action: pair.action)
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
|
215
VimR/VimR/RxRedux.swift
Normal file
215
VimR/VimR/RxRedux.swift
Normal file
@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import RxSwift
|
||||
|
||||
protocol ReduxContextType {
|
||||
|
||||
/**
|
||||
Type that holds the global app state
|
||||
|
||||
- Important:
|
||||
This type must be typealias'ed in `ReduxTypes` or in an extension thereof.
|
||||
*/
|
||||
associatedtype StateType
|
||||
|
||||
/**
|
||||
"The greatest common divisor" for all actions used in the app: Assuming it is set to `ReduxTypes.ActionType` type,
|
||||
the following must be true for any action
|
||||
```
|
||||
assert(someAction is ReduxTypes.ActionType) // which means
|
||||
let actionWithMinimumType: ReduxTypes.ActionType = anyAction
|
||||
```
|
||||
Most probably this type will be set to `Any`.
|
||||
|
||||
- Important:
|
||||
This type must be typealias'ed in `ReduxTypes` or in an extension thereof.
|
||||
*/
|
||||
associatedtype ActionType
|
||||
|
||||
typealias ReduceTuple = (state: StateType, action: ActionType, modified: Bool)
|
||||
typealias ReduceFunction = (ReduceTuple) -> ReduceTuple
|
||||
}
|
||||
|
||||
/**
|
||||
`typealias` `StateType` and `ActionType` either within the class definition or in an extension.
|
||||
*/
|
||||
class ReduxTypes: ReduxContextType {
|
||||
}
|
||||
|
||||
protocol ReducerType {
|
||||
|
||||
associatedtype StateType
|
||||
associatedtype ActionType
|
||||
|
||||
typealias ReduceTuple = (state: StateType, action: ActionType, modified: Bool)
|
||||
typealias ActionTypeErasedReduceTuple = (state: StateType, action: ReduxTypes.ActionType, modified: Bool)
|
||||
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple
|
||||
}
|
||||
|
||||
extension ReducerType {
|
||||
|
||||
func reduce(_ tuple: ActionTypeErasedReduceTuple) -> ActionTypeErasedReduceTuple {
|
||||
guard let typedTuple = tuple as? ReduceTuple else {
|
||||
return tuple
|
||||
}
|
||||
|
||||
let typedResult = self.typedReduce(typedTuple)
|
||||
return (state: typedResult.state, action: typedResult.action, modified: typedResult.modified)
|
||||
}
|
||||
}
|
||||
|
||||
protocol MiddlewareType {
|
||||
|
||||
associatedtype StateType
|
||||
associatedtype ActionType
|
||||
|
||||
typealias ReduceTuple = (state: StateType, action: ActionType, modified: Bool)
|
||||
typealias ActionTypeErasedReduceTuple = (state: StateType, action: ReduxTypes.ActionType, modified: Bool)
|
||||
|
||||
typealias TypedActionReduceFunction = (ReduceTuple) -> ActionTypeErasedReduceTuple
|
||||
typealias ActionTypeErasedReduceFunction = (ActionTypeErasedReduceTuple) -> ActionTypeErasedReduceTuple
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction
|
||||
}
|
||||
|
||||
extension MiddlewareType {
|
||||
|
||||
func apply(_ reduce: @escaping ActionTypeErasedReduceFunction) -> ActionTypeErasedReduceFunction {
|
||||
return { tuple in
|
||||
guard let typedTuple = tuple as? ReduceTuple else {
|
||||
return reduce(tuple)
|
||||
}
|
||||
|
||||
let typedReduce: (ReduceTuple) -> ActionTypeErasedReduceTuple = { typedTuple in
|
||||
// We know that we can cast the typed action to ReduxTypes.ActionType
|
||||
return reduce((state: typedTuple.state, action: typedTuple.action, modified: typedTuple.modified))
|
||||
}
|
||||
|
||||
return self.typedApply(typedReduce)(typedTuple)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol UiComponent {
|
||||
|
||||
associatedtype StateType
|
||||
|
||||
init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType)
|
||||
}
|
||||
|
||||
class ActionEmitter {
|
||||
|
||||
let observable: Observable<ReduxTypes.ActionType>
|
||||
|
||||
init() {
|
||||
self.observable = self.subject.asObservable().observeOn(scheduler)
|
||||
}
|
||||
|
||||
func typedEmit<T>() -> ((T) -> Void) {
|
||||
return { (action: T) in
|
||||
self.subject.onNext(action)
|
||||
}
|
||||
}
|
||||
|
||||
func terminate() {
|
||||
self.subject.onCompleted()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.subject.onCompleted()
|
||||
}
|
||||
|
||||
private let scheduler = SerialDispatchQueueScheduler(qos: .userInteractive)
|
||||
private let subject = PublishSubject<ReduxTypes.ActionType>()
|
||||
}
|
||||
|
||||
class ReduxContext {
|
||||
|
||||
let actionEmitter = ActionEmitter()
|
||||
let stateSource: Observable<ReduxTypes.StateType>
|
||||
|
||||
convenience init(initialState: ReduxTypes.StateType,
|
||||
reducers: [ReduxTypes.ReduceFunction],
|
||||
middlewares: [(@escaping ReduxTypes.ReduceFunction) -> ReduxTypes.ReduceFunction] = []) {
|
||||
|
||||
self.init(initialState: initialState)
|
||||
|
||||
self.actionEmitter.observable
|
||||
.map { (state: self.state, action: $0, modified: false) }
|
||||
.reduce(by: reducers, middlewares: middlewares)
|
||||
.filter { $0.modified }
|
||||
.subscribe(onNext: { tuple in
|
||||
self.state = tuple.state
|
||||
self.stateSubject.onNext(tuple.state)
|
||||
})
|
||||
.disposed(by: self.disposeBag)
|
||||
}
|
||||
|
||||
init(initialState: ReduxTypes.StateType) {
|
||||
self.state = initialState
|
||||
self.stateSource = self.stateSubject.asObservable().observeOn(self.stateScheduler)
|
||||
}
|
||||
|
||||
func terminate() {
|
||||
self.actionEmitter.terminate()
|
||||
self.stateSubject.onCompleted()
|
||||
}
|
||||
|
||||
var state: ReduxTypes.StateType
|
||||
|
||||
let stateSubject = PublishSubject<ReduxTypes.StateType>()
|
||||
let stateScheduler = SerialDispatchQueueScheduler(qos: .userInteractive)
|
||||
|
||||
let disposeBag = DisposeBag()
|
||||
}
|
||||
|
||||
extension Observable {
|
||||
|
||||
func completableSubject() -> CompletableSubject<Element> {
|
||||
return CompletableSubject(source: self)
|
||||
}
|
||||
}
|
||||
|
||||
class CompletableSubject<T> {
|
||||
|
||||
func asObservable() -> Observable<T> {
|
||||
return self.subject.asObservable()
|
||||
}
|
||||
|
||||
init(source: Observable<T>) {
|
||||
let subject = PublishSubject<T>()
|
||||
self.subscription = source.subscribe(onNext: { element in subject.onNext(element) })
|
||||
self.subject = subject
|
||||
}
|
||||
|
||||
func onCompleted() {
|
||||
self.subject.onCompleted()
|
||||
self.subscription.dispose()
|
||||
}
|
||||
|
||||
private let subject: PublishSubject<T>
|
||||
private let subscription: Disposable
|
||||
}
|
||||
|
||||
extension Observable {
|
||||
|
||||
func reduce(
|
||||
by reducers: [(Element) -> Element],
|
||||
middlewares: [(@escaping (Element) -> Element) -> (Element) -> Element]
|
||||
) -> Observable<Element> {
|
||||
|
||||
let dispatch = { pair in
|
||||
return reducers.reduce(pair) { result, reduceBody in
|
||||
return reduceBody(result)
|
||||
}
|
||||
}
|
||||
|
||||
let next = middlewares.reversed().reduce(dispatch) { result, middleware in middleware(result) }
|
||||
return self.map(next)
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
import Cocoa
|
||||
import RxSwift
|
||||
|
||||
extension ObservableType {
|
||||
extension Observable {
|
||||
|
||||
func mapOmittingNil<R>(_ transform: @escaping (E) throws -> R?) -> RxSwift.Observable<R> {
|
||||
return self
|
||||
@ -16,6 +16,13 @@ extension ObservableType {
|
||||
}
|
||||
}
|
||||
|
||||
extension PrimitiveSequenceType where TraitType == CompletableTrait, ElementType == Never {
|
||||
|
||||
func andThen(using body: () -> Completable) -> Completable {
|
||||
return self.andThen(body())
|
||||
}
|
||||
}
|
||||
|
||||
extension PrimitiveSequence where Element == Never, TraitType == CompletableTrait {
|
||||
|
||||
func wait() throws {
|
||||
@ -58,10 +65,6 @@ extension PrimitiveSequence where TraitType == SingleTrait {
|
||||
.ignoreElements()
|
||||
}
|
||||
|
||||
func asCompletable() -> Completable {
|
||||
return self.asObservable().ignoreElements()
|
||||
}
|
||||
|
||||
func syncValue() -> Element? {
|
||||
var trigger = false
|
||||
var value: Element?
|
||||
|
@ -233,6 +233,7 @@ extension MainWindow {
|
||||
var frame = CGRect(x: 100, y: 100, width: 600, height: 400)
|
||||
|
||||
////// transient
|
||||
var goToLineFromCli: Marked<Int>?
|
||||
var lastFileSystemUpdate = Marked(FileUtils.userHomeUrl)
|
||||
|
||||
var tools = WorkspaceToolState.default
|
||||
@ -260,6 +261,7 @@ extension MainWindow {
|
||||
var useInteractiveZsh = false
|
||||
var nvimArgs: [String]?
|
||||
var cliPipePath: String?
|
||||
var envDict: [String: String]?
|
||||
|
||||
var isLeftOptionMeta = false
|
||||
var isRightOptionMeta = false
|
||||
|
@ -5,11 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class ToolsPrefReducer {
|
||||
class ToolsPrefReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, ToolsPref.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = ToolsPref.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
|
||||
switch pair.action {
|
||||
@ -19,6 +20,6 @@ class ToolsPrefReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -6,49 +6,19 @@
|
||||
import Foundation
|
||||
import RxSwift
|
||||
|
||||
protocol UiComponent {
|
||||
struct StateActionPair<S, A> {
|
||||
|
||||
associatedtype StateType
|
||||
|
||||
init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType)
|
||||
var state: S
|
||||
var action: A
|
||||
var modified: Bool
|
||||
}
|
||||
|
||||
class ActionEmitter {
|
||||
protocol UuidTagged {
|
||||
|
||||
let observable: Observable<Any>
|
||||
|
||||
init() {
|
||||
self.observable = self.subject.asObservable().observeOn(scheduler)
|
||||
}
|
||||
|
||||
func typedEmit<T>() -> ((T) -> Void) {
|
||||
return { (action: T) in
|
||||
self.subject.onNext(action)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.subject.onCompleted()
|
||||
}
|
||||
|
||||
private let scheduler = SerialDispatchQueueScheduler(qos: .userInteractive)
|
||||
private let subject = PublishSubject<Any>()
|
||||
var uuid: String { get }
|
||||
}
|
||||
|
||||
class StateActionPair<S, A> {
|
||||
|
||||
let modified: Bool
|
||||
let state: S
|
||||
let action: A
|
||||
|
||||
init(state: S, action: A, modified: Bool = true) {
|
||||
self.modified = modified
|
||||
self.state = state
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
class UuidAction<A>: CustomStringConvertible {
|
||||
class UuidAction<A>: UuidTagged, CustomStringConvertible {
|
||||
|
||||
let uuid: String
|
||||
let payload: A
|
||||
@ -63,7 +33,7 @@ class UuidAction<A>: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
class UuidState<S>: CustomStringConvertible {
|
||||
class UuidState<S>: UuidTagged, CustomStringConvertible {
|
||||
|
||||
let uuid: String
|
||||
let payload: S
|
||||
@ -116,23 +86,6 @@ class Marked<T>: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
extension Observable {
|
||||
|
||||
func reduce(by reduce: @escaping (Element) -> Element) -> Observable<Element> {
|
||||
return self.map(reduce)
|
||||
}
|
||||
|
||||
func apply(_ apply: @escaping (Element) -> Void) -> Observable<Element> {
|
||||
return self.do(onNext: apply)
|
||||
}
|
||||
|
||||
func filterMapPair<S, A>() -> Observable<S> where Element == StateActionPair<S, A> {
|
||||
return self
|
||||
.filter { $0.modified }
|
||||
.map { $0.state }
|
||||
}
|
||||
}
|
||||
|
||||
class UiComponentTemplate: UiComponent {
|
||||
|
||||
typealias StateType = State
|
||||
|
@ -80,25 +80,19 @@ class UiRoot: UiComponent {
|
||||
private let prefWindow: PrefWindow
|
||||
|
||||
private var mainWindows = [String: MainWindow]()
|
||||
private var subjectForMainWindows = [String: PublishSubject<MainWindow.State>]()
|
||||
private var disposables = [String: Disposable]()
|
||||
private var subjectForMainWindows = [String: CompletableSubject<MainWindow.State>]()
|
||||
|
||||
private func newMainWindow(with state: MainWindow.State) -> MainWindow {
|
||||
let subject = PublishSubject<MainWindow.State>()
|
||||
let source = self.source.mapOmittingNil { $0.mainWindows[state.uuid] }
|
||||
let subject = self.source.mapOmittingNil { $0.mainWindows[state.uuid] }.completableSubject()
|
||||
|
||||
self.subjectForMainWindows[state.uuid] = subject
|
||||
self.disposables[state.uuid] = source.subscribe(subject)
|
||||
|
||||
return MainWindow(source: subject.asObservable(), emitter: self.emitter, state: state)
|
||||
}
|
||||
|
||||
private func removeMainWindow(with uuid: String) {
|
||||
self.subjectForMainWindows[uuid]?.onCompleted()
|
||||
self.disposables[uuid]?.dispose()
|
||||
|
||||
self.subjectForMainWindows.removeValue(forKey: uuid)
|
||||
self.disposables.removeValue(forKey: uuid)
|
||||
self.mainWindows.removeValue(forKey: uuid)
|
||||
}
|
||||
}
|
||||
|
@ -5,92 +5,100 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class UiRootReducer {
|
||||
class UiRootReducer: ReducerType {
|
||||
|
||||
typealias UiRootPair = StateActionPair<AppState, UiRoot.Action>
|
||||
typealias MainWindowPair = StateActionPair<AppState, UuidAction<MainWindow.Action>>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = UiRoot.Action
|
||||
|
||||
func reduceUiRoot(_ pair: UiRootPair) -> UiRootPair {
|
||||
var appState = pair.state
|
||||
let mainWindow = MainWindowReducer()
|
||||
|
||||
switch pair.action {
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var appState = tuple.state
|
||||
|
||||
switch tuple.action {
|
||||
|
||||
case .quit:
|
||||
appState.quit = true
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: appState, action: pair.action)
|
||||
return (appState, tuple.action, true)
|
||||
}
|
||||
|
||||
func reduceMainWindow(_ pair: MainWindowPair) -> MainWindowPair {
|
||||
var appState = pair.state
|
||||
let uuid = pair.action.uuid
|
||||
class MainWindowReducer: ReducerType {
|
||||
|
||||
switch pair.action.payload {
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
case let .becomeKey(isFullScreen):
|
||||
appState.currentMainWindowUuid = uuid
|
||||
appState.mainWindowTemplate = self.mainWindowTemplate(
|
||||
from: appState.mainWindowTemplate,
|
||||
new: appState.mainWindows[uuid] ?? appState.mainWindowTemplate,
|
||||
isFullScreen: isFullScreen
|
||||
)
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var appState = tuple.state
|
||||
let uuid = tuple.action.uuid
|
||||
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .becomeKey(isFullScreen):
|
||||
appState.currentMainWindowUuid = uuid
|
||||
appState.mainWindowTemplate = self.mainWindowTemplate(
|
||||
from: appState.mainWindowTemplate,
|
||||
new: appState.mainWindows[uuid] ?? appState.mainWindowTemplate,
|
||||
isFullScreen: isFullScreen
|
||||
)
|
||||
|
||||
case let .frameChanged(to:frame):
|
||||
if uuid == appState.currentMainWindowUuid {
|
||||
appState.mainWindowTemplate.frame = frame
|
||||
}
|
||||
|
||||
case let .setToolsState(tools):
|
||||
appState.mainWindowTemplate.orderedTools = tools.map { $0.0 }
|
||||
|
||||
case let .toggleAllTools(value):
|
||||
appState.mainWindowTemplate.isAllToolsVisible = value
|
||||
|
||||
case let .toggleToolButtons(value):
|
||||
appState.mainWindowTemplate.isToolButtonsVisible = value
|
||||
|
||||
case .close:
|
||||
if appState.currentMainWindowUuid == uuid, let mainWindowToClose = appState.mainWindows[uuid] {
|
||||
appState.mainWindowTemplate = self.mainWindowTemplate(from: appState.mainWindowTemplate,
|
||||
new: mainWindowToClose,
|
||||
isFullScreen: false)
|
||||
|
||||
appState.currentMainWindowUuid = nil
|
||||
}
|
||||
|
||||
appState.mainWindows.removeValue(forKey: uuid)
|
||||
|
||||
case let .setTheme(theme):
|
||||
appState.mainWindowTemplate.appearance.theme = Marked(theme)
|
||||
|
||||
default:
|
||||
return tuple
|
||||
|
||||
case let .frameChanged(to:frame):
|
||||
if uuid == appState.currentMainWindowUuid {
|
||||
appState.mainWindowTemplate.frame = frame
|
||||
}
|
||||
|
||||
case let .setToolsState(tools):
|
||||
appState.mainWindowTemplate.orderedTools = tools.map { $0.0 }
|
||||
return (appState, tuple.action, true)
|
||||
}
|
||||
|
||||
case let .toggleAllTools(value):
|
||||
appState.mainWindowTemplate.isAllToolsVisible = value
|
||||
private func mainWindowTemplate(from old: MainWindow.State,
|
||||
new: MainWindow.State,
|
||||
isFullScreen: Bool) -> MainWindow.State {
|
||||
|
||||
case let .toggleToolButtons(value):
|
||||
appState.mainWindowTemplate.isToolButtonsVisible = value
|
||||
var result = old
|
||||
|
||||
case .close:
|
||||
if appState.currentMainWindowUuid == uuid, let mainWindowToClose = appState.mainWindows[uuid] {
|
||||
appState.mainWindowTemplate = self.mainWindowTemplate(from: appState.mainWindowTemplate,
|
||||
new: mainWindowToClose,
|
||||
isFullScreen: false)
|
||||
|
||||
appState.currentMainWindowUuid = nil
|
||||
if !isFullScreen {
|
||||
result.frame = new.frame
|
||||
}
|
||||
|
||||
appState.mainWindows.removeValue(forKey: uuid)
|
||||
|
||||
case let .setTheme(theme):
|
||||
appState.mainWindowTemplate.appearance.theme = Marked(theme)
|
||||
|
||||
default:
|
||||
return pair
|
||||
result.isAllToolsVisible = new.isAllToolsVisible
|
||||
result.isToolButtonsVisible = new.isToolButtonsVisible
|
||||
result.tools = new.tools
|
||||
result.orderedTools = new.orderedTools
|
||||
result.previewTool = new.previewTool
|
||||
result.fileBrowserShowHidden = new.fileBrowserShowHidden
|
||||
result.htmlPreview = .default
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
return StateActionPair(state: appState, action: pair.action)
|
||||
}
|
||||
|
||||
private func mainWindowTemplate(from old: MainWindow.State,
|
||||
new: MainWindow.State,
|
||||
isFullScreen: Bool) -> MainWindow.State {
|
||||
|
||||
var result = old
|
||||
|
||||
if !isFullScreen {
|
||||
result.frame = new.frame
|
||||
}
|
||||
|
||||
result.isAllToolsVisible = new.isAllToolsVisible
|
||||
result.isToolButtonsVisible = new.isToolButtonsVisible
|
||||
result.tools = new.tools
|
||||
result.orderedTools = new.orderedTools
|
||||
result.previewTool = new.previewTool
|
||||
result.fileBrowserShowHidden = new.fileBrowserShowHidden
|
||||
result.htmlPreview = .default
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
148
VimR/VimR/vimr
148
VimR/VimR/vimr
@ -5,6 +5,7 @@ import subprocess
|
||||
import argparse
|
||||
import os
|
||||
import uuid
|
||||
import json
|
||||
|
||||
|
||||
class Action:
|
||||
@ -17,13 +18,15 @@ class Action:
|
||||
|
||||
class QueryParamKey:
|
||||
PIPE_PATH = "pipe-path"
|
||||
ENV_PATH = "env-path"
|
||||
CWD = "cwd"
|
||||
FILE = "file"
|
||||
NVIM_ARGS = "nvim-args"
|
||||
WAIT = "wait"
|
||||
LINE = "line"
|
||||
|
||||
|
||||
def wait_for_ui_to_close():
|
||||
def wait_for_ui_to_close(pipe_path):
|
||||
with open(pipe_path, 'r') as fifo:
|
||||
while True:
|
||||
if len(fifo.read()) == 0:
|
||||
@ -31,7 +34,6 @@ def wait_for_ui_to_close():
|
||||
|
||||
|
||||
def call_open(action, query_params, args):
|
||||
query_params[QueryParamKey.PIPE_PATH] = pipe_path
|
||||
if args.wait:
|
||||
query_params[QueryParamKey.WAIT] = "true"
|
||||
|
||||
@ -47,10 +49,8 @@ def abspath(path):
|
||||
return os.path.abspath(os.path.expanduser(path))
|
||||
|
||||
|
||||
def vimr_nvim(other_args, nvim_args):
|
||||
query_params = {
|
||||
QueryParamKey.CWD: os.getcwd()
|
||||
}
|
||||
def vimr_nvim(other_args, nvim_args, query_params):
|
||||
query_params[QueryParamKey.CWD] = os.getcwd()
|
||||
|
||||
if nvim_args:
|
||||
query_params[QueryParamKey.NVIM_ARGS] = nvim_args
|
||||
@ -58,14 +58,12 @@ def vimr_nvim(other_args, nvim_args):
|
||||
call_open(Action.NVIM, query_params, other_args)
|
||||
|
||||
|
||||
def vimr(action, args):
|
||||
def vimr(action, args, query_params):
|
||||
cwd = os.getcwd()
|
||||
if args.cwd is not None:
|
||||
cwd = abspath(args.cwd)
|
||||
|
||||
query_params = {
|
||||
QueryParamKey.CWD: cwd
|
||||
}
|
||||
query_params[QueryParamKey.CWD] = cwd
|
||||
|
||||
files = args.file
|
||||
if files:
|
||||
@ -74,67 +72,99 @@ def vimr(action, args):
|
||||
call_open(action, query_params, args)
|
||||
|
||||
|
||||
pipe_path = "/tmp/com_qvacua_vimr_cli_pipe_{0}".format(str(uuid.uuid4()))
|
||||
def main(args):
|
||||
uuid_str = str(uuid.uuid4())
|
||||
pipe_path = "/tmp/com_qvacua_vimr_cli_pipe_{0}".format(uuid_str)
|
||||
if os.path.exists(pipe_path):
|
||||
os.remove(pipe_path)
|
||||
|
||||
try:
|
||||
os.mkfifo(pipe_path, 0600)
|
||||
except OSError as error:
|
||||
print("ERROR: {0}\n"
|
||||
"{1} could not be mkfifo'ed.\n"
|
||||
"Please go to https://github.com/qvacua/vimr and create an issue.".format(error, pipe_path))
|
||||
raise
|
||||
|
||||
query_params = {
|
||||
QueryParamKey.PIPE_PATH: pipe_path
|
||||
}
|
||||
|
||||
if args.line is not None:
|
||||
query_params[QueryParamKey.LINE] = args.line
|
||||
|
||||
if args.cur_env:
|
||||
env_file = "/tmp/com_qvacua_vimr_env_{0}".format(uuid_str)
|
||||
with open(env_file, "w") as f:
|
||||
f.write(json.dumps({k: v for (k, v) in os.environ.items()}))
|
||||
os.chmod(env_file, 0600)
|
||||
query_params[QueryParamKey.ENV_PATH] = env_file
|
||||
|
||||
if args.nvim:
|
||||
nvim_parser = argparse.ArgumentParser()
|
||||
nvim_parser.add_argument("--nvim", action="store_true")
|
||||
nvim_parser.add_argument("--wait", action="store_true")
|
||||
nvim_parser.add_argument("--cur-env", action="store_true")
|
||||
nvim_parser.add_argument("--dry-run", action="store_true")
|
||||
nvim_parser.add_argument("--line", action="store")
|
||||
other_args, nvim_args = nvim_parser.parse_known_args()
|
||||
vimr_nvim(other_args, nvim_args, query_params)
|
||||
|
||||
else:
|
||||
if not args.file:
|
||||
action = Action.ACTIVATE
|
||||
elif args.new_window:
|
||||
action = Action.NEW_WINDOW
|
||||
elif args.separate_windows:
|
||||
action = Action.SEPARATE_WINDOWS
|
||||
else:
|
||||
action = Action.OPEN
|
||||
|
||||
vimr(action, args, query_params)
|
||||
|
||||
if args.dry_run:
|
||||
exit(0)
|
||||
|
||||
wait_for_ui_to_close(pipe_path)
|
||||
|
||||
if os.path.exists(pipe_path):
|
||||
os.remove(pipe_path)
|
||||
|
||||
try:
|
||||
os.mkfifo(pipe_path, 0600)
|
||||
except OSError as error:
|
||||
print("ERROR: {0}\n"
|
||||
"{1} could not be mkfifo'ed.\n"
|
||||
"Please go to https://github.com/qvacua/vimr and create an issue.".format(error, pipe_path))
|
||||
raise
|
||||
|
||||
description = """
|
||||
def parse_args():
|
||||
description = """
|
||||
Open files in VimR: By default all files are open in tabs in the front most window or in a new window if there is none.
|
||||
The working directory will be set to the current directory.
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser(description=description)
|
||||
parser = argparse.ArgumentParser(description=description)
|
||||
|
||||
parser.add_argument("--dry-run", action="store_true", dest="dry_run", help="Just print the 'open' command.")
|
||||
parser.add_argument("--cwd", action="store", help="Set the working directory.")
|
||||
parser.add_argument("--wait",
|
||||
action="store_true",
|
||||
help="This command line tool will exit when the corresponding UI window is closed.")
|
||||
parser.add_argument("--nvim",
|
||||
parser.add_argument("--dry-run", action="store_true", dest="dry_run", help="Just print the 'open' command.")
|
||||
parser.add_argument("--cwd", action="store", help="Set the working directory.")
|
||||
parser.add_argument("--line", action="store", help="Go to line")
|
||||
parser.add_argument("--wait",
|
||||
action="store_true",
|
||||
help="All other arguments (except --dry-run and --wait) will be passed over to nvim.")
|
||||
help="This command line tool will exit when the corresponding UI window is closed.")
|
||||
parser.add_argument("--nvim",
|
||||
action="store_true",
|
||||
help="All arguments except --cur-env, --line, --dry-run and --wait will be passed "
|
||||
"over to the (new) nvim instance in a new UI window.")
|
||||
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
# no option => Open files in tabs in the front most window.
|
||||
group.add_argument("-n", action="store_true", dest="new_window", help="Open files in tabs in a new window.")
|
||||
group.add_argument("-s", action="store_true", dest="separate_windows", help="Open files in separate windows.")
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
# no option => Open files in tabs in the front most window.
|
||||
group.add_argument("--cur-env",
|
||||
action="store_true",
|
||||
dest="cur_env",
|
||||
help="Use the current environment variables when launching the background neovim process. "
|
||||
"All files will be opened in a new window.")
|
||||
group.add_argument("-n", action="store_true", dest="new_window", help="Open files in tabs in a new window.")
|
||||
group.add_argument("-s", action="store_true", dest="separate_windows", help="Open files in separate windows.")
|
||||
|
||||
parser.add_argument("file", nargs="*")
|
||||
parser.add_argument("file", nargs="*")
|
||||
|
||||
args, _ = parser.parse_known_args()
|
||||
args, _ = parser.parse_known_args()
|
||||
return args
|
||||
|
||||
if args.nvim:
|
||||
nvim_parser = argparse.ArgumentParser()
|
||||
nvim_parser.add_argument("--nvim", action="store_true")
|
||||
nvim_parser.add_argument("--wait", action="store_true")
|
||||
nvim_parser.add_argument("--dry-run", action="store_true")
|
||||
other_args, nvim_args = nvim_parser.parse_known_args()
|
||||
vimr_nvim(other_args, nvim_args)
|
||||
|
||||
else:
|
||||
if not args.file:
|
||||
action = Action.ACTIVATE
|
||||
elif args.new_window:
|
||||
action = Action.NEW_WINDOW
|
||||
elif args.separate_windows:
|
||||
action = Action.SEPARATE_WINDOWS
|
||||
else:
|
||||
action = Action.OPEN
|
||||
|
||||
vimr(action, args)
|
||||
|
||||
if args.dry_run:
|
||||
exit(0)
|
||||
|
||||
wait_for_ui_to_close()
|
||||
|
||||
os.remove(pipe_path)
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
main(args)
|
||||
|
@ -15,10 +15,10 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.24.0</string>
|
||||
<string>SNAPSHOT-284</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>282</string>
|
||||
<string>284</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -7,35 +7,22 @@
|
||||
<description>Most recent changes with links to updates for VimR.</description>
|
||||
<language>en</language>
|
||||
<item>
|
||||
<title>v0.24.0-282</title>
|
||||
<title>SNAPSHOT-284</title>
|
||||
<description><![CDATA[
|
||||
<ul>
|
||||
<li>Neovim 0.3.0</li>
|
||||
<li>Some refactorings for the Neovim and the UI interface.</li>
|
||||
<li>GH-402: Add file associations; using definitions and icons from <a href="http://macvim.org/">MacVim</a></li>
|
||||
<li>GH-636: Bugfix: double cursor when entering terminal</li>
|
||||
<li>GH-653: Bugfix: Crashes when closing the last window with "Quit after last window closes"-option turned on.</li>
|
||||
<li>Bugfix: Crashes when <code>vimr --wait</code> is used, but is <code>Ctlr-C</code>'ed before closing the UI window.</li>
|
||||
<li>Bugfix: <code>vimr --wait SOME_FILE</code> does not exit.</li>
|
||||
<li>Use LuaJIT again.</li>
|
||||
<li>Dependencies updates:<ul>
|
||||
<li>sparkle-project/Sparkle@1.19.0</li>
|
||||
<li>Quick/nimble@7.1.2</li>
|
||||
<li>eonil/FileSystemEvents@1.0.0</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>GH-443: <code>vimr --line ${LINE_NUMBER} ${SOME_FILE}</code> will open the file and go to the given line. If the file is already open in a UI window, then that window will be selected and the cursor will be moved to the given line. This can be used e.g. to reverse-search LaTeX. Note that you have to re-install the <code>vimr</code> tool.</li>
|
||||
</ul>
|
||||
]]></description>
|
||||
<releaseNotesLink>
|
||||
https://github.com/qvacua/vimr/releases/tag/v0.24.0-282
|
||||
https://github.com/qvacua/vimr/releases/tag/snapshot/284
|
||||
</releaseNotesLink>
|
||||
<pubDate>2018-07-02T19:16:46.907124</pubDate>
|
||||
<pubDate>2018-07-06T21:01:36.974431</pubDate>
|
||||
<minimumSystemVersion>10.10.0</minimumSystemVersion>
|
||||
<enclosure url="https://github.com/qvacua/vimr/releases/download/v0.24.0-282/VimR-v0.24.0-282.tar.bz2"
|
||||
sparkle:version="282"
|
||||
sparkle:shortVersionString="0.24.0"
|
||||
sparkle:dsaSignature="MCwCFDolgerESMrUpxwmcdVot47aoPiHAhQ0qFQcMrpyTZ23onJ/zv8XaaHHSA=="
|
||||
length="14119123"
|
||||
<enclosure url="https://github.com/qvacua/vimr/releases/download/snapshot/284/VimR-SNAPSHOT-284.tar.bz2"
|
||||
sparkle:version="284"
|
||||
sparkle:shortVersionString="SNAPSHOT-284"
|
||||
sparkle:dsaSignature="MC4CFQC85W5DOK+chBghYPusLQGPdfrXWAIVAOKVRd3YSnGyXJ3w5S3F7LU4vJTM"
|
||||
length="14182159"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
</channel>
|
||||
|
@ -1,3 +1,12 @@
|
||||
# 0.25.0-???
|
||||
|
||||
* GH-625: `vimr --cur-env` will pass the current environment variables to the new neovim process. This will result in `virtualenv` support.
|
||||
* GH-443: `vimr --line ${LINE_NUMBER} ${SOME_FILE}` will open the file and go to the given line. If the file is already open in a UI window, then that window will be selected and the cursor will be moved to the given line. This can be used e.g. to reverse-search LaTeX.
|
||||
* Dependencies updates:
|
||||
- ReactiveX/RxSwift@4.2.0
|
||||
- httpswift/swifter@1.4.2
|
||||
- `MMCoreTextView` of Macvim: macvim-dev/macvim@351faf929e4abe32ea4cc31078d1a625fc86a69f
|
||||
|
||||
# 0.24.0-282
|
||||
|
||||
* Neovim 0.3.0
|
||||
|
Loading…
Reference in New Issue
Block a user