1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-24 22:33:52 +03:00

Merge remote-tracking branch 'origin/develop' into update-neovim

Conflicts:
	RxPack/RxNeovimApi.generated.swift
This commit is contained in:
Tae Won Ha 2020-01-26 22:19:24 +01:00
commit ea47d16617
No known key found for this signature in database
GPG Key ID: E40743465B5B8B44
24 changed files with 527 additions and 735 deletions

View File

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.30.0</string>
<string>SNAPSHOT-336</string>
<key>CFBundleVersion</key>
<string>335</string>
<string>336</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>

View File

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.30.0</string>
<string>SNAPSHOT-336</string>
<key>CFBundleVersion</key>
<string>335</string>
<string>336</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>

View File

@ -38,9 +38,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.30.0</string>
<string>SNAPSHOT-336</string>
<key>CFBundleVersion</key>
<string>335</string>
<string>336</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>

View File

@ -1146,7 +1146,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 335;
CURRENT_PROJECT_VERSION = 336;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@ -1208,7 +1208,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 335;
CURRENT_PROJECT_VERSION = 336;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@ -1238,7 +1238,7 @@
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 335;
DYLIB_CURRENT_VERSION = 336;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac";
FRAMEWORK_VERSION = A;
@ -1260,7 +1260,7 @@
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 335;
DYLIB_CURRENT_VERSION = 336;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac";
FRAMEWORK_VERSION = A;

View File

@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.30.0</string>
<string>SNAPSHOT-336</string>
<key>CFBundleVersion</key>
<string>335</string>
<string>336</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2017 Tae Won Ha. All rights reserved.</string>
<key>NSPrincipalClass</key>

View File

@ -9,7 +9,7 @@ import RxSwift
extension RxNeovimApi {
public func getDirtyStatus(
checkBlocked: Bool = true
errWhenBlocked: Bool = true
) -> Single<Bool> {
let params: [RxNeovimApi.Value] = [
@ -23,7 +23,7 @@ extension RxNeovimApi {
return result
}
if checkBlocked {
if errWhenBlocked {
return self
.checkBlocked(
self.rpc(method: "nvim_get_dirty_status", params: params, expectsReturnValue: true)
@ -38,7 +38,7 @@ extension RxNeovimApi {
public func bufGetInfo(
buffer: RxNeovimApi.Buffer,
checkBlocked: Bool = true
errWhenBlocked: Bool = true
) -> Single<Dictionary<String, RxNeovimApi.Value>> {
let params: [RxNeovimApi.Value] = [
@ -53,7 +53,7 @@ extension RxNeovimApi {
return result
}
if checkBlocked {
if errWhenBlocked {
return self
.checkBlocked(
self.rpc(method: "nvim_buf_get_info", params: params)

View File

@ -41,7 +41,7 @@ extension NvimView {
let finalInput = isWrapNeeded ? self.wrapNamedKeys(flags + namedChars)
: self.vimPlainString(chars)
_ = self.api.input(keys: finalInput, checkBlocked: false).syncValue()
_ = self.api.input(keys: finalInput, errWhenBlocked: false).syncValue()
self.keyDownDone = true
}
@ -130,7 +130,7 @@ extension NvimView {
// So we escape as early as possible
if chars == "\0" {
self.api
.input(keys: self.wrapNamedKeys("Nul"), checkBlocked: false)
.input(keys: self.wrapNamedKeys("Nul"), errWhenBlocked: false)
.subscribe()
.disposed(by: self.disposeBag)
return true
@ -141,7 +141,7 @@ extension NvimView {
// Also mentioned in MacVim's KeyBindings.plist
if .control == flags && chars == "6" {
self.api
.input(keys: "\u{1e}", checkBlocked: false) // AKA ^^
.input(keys: "\u{1e}", errWhenBlocked: false) // AKA ^^
.subscribe()
.disposed(by: self.disposeBag)
return true
@ -150,7 +150,7 @@ extension NvimView {
if .control == flags && chars == "2" {
// <C-2> should generate \0, escaping as above
self.api
.input(keys: self.wrapNamedKeys("Nul"), checkBlocked: false)
.input(keys: self.wrapNamedKeys("Nul"), errWhenBlocked: false)
.subscribe()
.disposed(by: self.disposeBag)
return true

View File

@ -50,14 +50,14 @@ extension NvimView {
switch self.mode {
case .insert, .replace:
self.api
.input(keys: "<Esc>ui", checkBlocked: false)
.input(keys: "<Esc>ui", errWhenBlocked: false)
.subscribe(onError: { error in
self.eventsSubject.onNext(.apiError(msg: "Could not undo", cause: error))
})
.disposed(by: self.disposeBag)
case .normal, .visual:
self.api
.input(keys: "u", checkBlocked: false)
.input(keys: "u", errWhenBlocked: false)
.subscribe(onError: { error in
self.eventsSubject.onNext(.apiError(msg: "Could not undo", cause: error))
})
@ -71,14 +71,14 @@ extension NvimView {
switch self.mode {
case .insert, .replace:
self.api
.input(keys: "<Esc><C-r>i", checkBlocked: false)
.input(keys: "<Esc><C-r>i", errWhenBlocked: false)
.subscribe(onError: { error in
self.eventsSubject.onNext(.apiError(msg: "Could not redo", cause: error))
})
.disposed(by: self.disposeBag)
case .normal, .visual:
self.api
.input(keys: "<C-r>", checkBlocked: false)
.input(keys: "<C-r>", errWhenBlocked: false)
.subscribe(onError: { error in
self.eventsSubject.onNext(.apiError(msg: "Could not redo", cause: error))
})
@ -92,7 +92,7 @@ extension NvimView {
switch self.mode {
case .visual, .normal:
self.api
.input(keys: "\"+d", checkBlocked: false)
.input(keys: "\"+d", errWhenBlocked: false)
.subscribe(onError: { error in
self.eventsSubject.onNext(.apiError(msg: "Could not cut", cause: error))
})
@ -106,7 +106,7 @@ extension NvimView {
switch self.mode {
case .visual, .normal:
self.api
.input(keys: "\"+y", checkBlocked: false)
.input(keys: "\"+y", errWhenBlocked: false)
.subscribe(onError: { error in
self.eventsSubject.onNext(.apiError(msg: "Could not copy", cause: error))
})
@ -125,7 +125,7 @@ extension NvimView {
|| self.mode == .replace
|| self.mode == .termFocus {
self.api
.input(keys: self.vimPlainString(content), checkBlocked: false)
.input(keys: self.vimPlainString(content), errWhenBlocked: false)
.subscribe(onError: { error in
self.eventsSubject.onNext(.apiError(msg: "Could not paste \(content)", cause: error))
})
@ -156,12 +156,12 @@ extension NvimView {
case .insert:
let cmd = element.column == 0 ? "<ESC>\"+Pa" : "<ESC>\"+pa"
return self.api
.input(keys: cmd, checkBlocked: false).asCompletable()
.input(keys: cmd, errWhenBlocked: false).asCompletable()
.andThen(Single.just(element.pasteModeSet))
case .normal, .visual:
return self.api
.input(keys: "\"+p", checkBlocked: false).asCompletable()
.input(keys: "\"+p", errWhenBlocked: false).asCompletable()
.andThen(Single.just(element.pasteModeSet))
default:
@ -186,7 +186,7 @@ extension NvimView {
switch self.mode {
case .normal, .visual:
self.api
.input(keys: "x", checkBlocked: false)
.input(keys: "x", errWhenBlocked: false)
.subscribe(onError: { error in
self.eventsSubject.onNext(.apiError(msg: "Could not delete", cause: error))
})
@ -200,14 +200,14 @@ extension NvimView {
switch self.mode {
case .insert, .visual:
self.api
.input(keys: "<Esc>ggVG", checkBlocked: false)
.input(keys: "<Esc>ggVG", errWhenBlocked: false)
.subscribe(onError: { error in
self.eventsSubject.onNext(.apiError(msg: "Could not select all", cause: error))
})
.disposed(by: self.disposeBag)
default:
self.api
.input(keys: "ggVG", checkBlocked: false)
.input(keys: "ggVG", errWhenBlocked: false)
.subscribe(onError: { error in
self.eventsSubject.onNext(.apiError(msg: "Could not select all", cause: error))
})

View File

@ -34,9 +34,9 @@ extension NvimView {
modifierFlags: event.modifierFlags,
cellPosition: cellPosition)
self.api
.input(keys: vimInputX, checkBlocked: false).asCompletable()
.input(keys: vimInputX, errWhenBlocked: false).asCompletable()
.andThen(
self.api.input(keys: vimInputY, checkBlocked: false).asCompletable()
self.api.input(keys: vimInputY, errWhenBlocked: false).asCompletable()
)
.subscribe()
.disposed(by: self.disposeBag)
@ -120,7 +120,7 @@ extension NvimView {
}
self.api
.input(keys: result, checkBlocked: false)
.input(keys: result, errWhenBlocked: false)
.subscribe()
.disposed(by: self.disposeBag)
}

View File

@ -65,17 +65,17 @@ public class NvimView: NSView,
self.updateFontMetaData(self._font)
}
}
public var characterspacing: CGFloat {
get {
return self._characterspacing
}
set {
guard newValue >= 0.0 else {
return
}
self._characterspacing = newValue
self.updateFontMetaData(self._font)
}
@ -121,10 +121,6 @@ public class NvimView: NSView,
return true
}
public let queue = DispatchQueue(
label: String(reflecting: NvimView.self),
qos: .userInteractive
)
public let scheduler: SerialDispatchQueueScheduler
public internal(set) var currentPosition = Position.beginning
@ -140,9 +136,11 @@ public class NvimView: NSView,
characterspacing: self._characterspacing,
usesLigatures: self.usesLigatures
)
self.bridge = UiBridge(uuid: self.uuid, queue: self.queue, config: config)
self.scheduler = SerialDispatchQueueScheduler(queue: self.queue,
internalSerialQueueName: "com.qvacua.NvimView.NvimView")
self.bridge = UiBridge(uuid: self.uuid, config: config)
self.scheduler = SerialDispatchQueueScheduler(
queue: self.queue,
internalSerialQueueName: "com.qvacua.NvimView.NvimView"
)
self.sourceFileUrls = config.sourceFiles
@ -154,8 +152,6 @@ public class NvimView: NSView,
self.cellSize = FontUtils.cellSize(
of: self.font, linespacing: self.linespacing, characterspacing: self.characterspacing
)
self.api.queue = self.queue
}
convenience override public init(frame rect: NSRect) {
@ -182,6 +178,11 @@ public class NvimView: NSView,
}
// MARK: - Internal
let queue = DispatchQueue(
label: String(reflecting: NvimView.self),
qos: .userInteractive
)
let bridge: UiBridge
let api = RxNeovimApi()

View File

@ -41,7 +41,7 @@ class UiBridge {
weak var consumer: UiBridgeConsumer?
init(uuid: UUID, queue: DispatchQueue, config: NvimView.Config) {
init(uuid: UUID, config: NvimView.Config) {
self.uuid = uuid
self.useInteractiveZsh = config.useInteractiveZsh
@ -60,11 +60,10 @@ class UiBridge {
self.log.debug("Using ENVs from login shell: \(self.envDict)")
}
self.queue = queue
self.scheduler = SerialDispatchQueueScheduler(queue: queue,
internalSerialQueueName: String(reflecting: UiBridge.self))
self.client.queue = self.queue
self.server.queue = self.queue
self.scheduler = SerialDispatchQueueScheduler(
queue: queue,
internalSerialQueueName: String(reflecting: UiBridge.self)
)
self.server.stream
.subscribe(onNext: { [weak self] message in
@ -334,7 +333,10 @@ class UiBridge {
private var runLocalServerAndNvimCompletable: Completable.CompletableObserver?
private let scheduler: SerialDispatchQueueScheduler
private let queue: DispatchQueue
private let queue = DispatchQueue(
label: String(reflecting: UiBridge.self),
qos: .userInitiated
)
private let disposeBag = DisposeBag()

View File

@ -15,8 +15,8 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>0.30.0</string>
<string>SNAPSHOT-336</string>
<key>CFBundleVersion</key>
<string>335</string>
<string>336</string>
</dict>
</plist>

View File

@ -17,25 +17,12 @@ public final class RxMessagePortClient {
public static func responseCodeToString(code: Int32) -> String {
switch code {
case kCFMessagePortSendTimeout:
return "kCFMessagePortSendTimeout"
case kCFMessagePortReceiveTimeout:
return "kCFMessagePortReceiveTimeout"
case kCFMessagePortIsInvalid:
return "kCFMessagePortIsInvalid"
case kCFMessagePortTransportError:
return "kCFMessagePortTransportError"
case kCFMessagePortBecameInvalidError:
return "kCFMessagePortBecameInvalidError"
default:
return "unknown"
case kCFMessagePortSendTimeout:return "kCFMessagePortSendTimeout"
case kCFMessagePortReceiveTimeout:return "kCFMessagePortReceiveTimeout"
case kCFMessagePortIsInvalid:return "kCFMessagePortIsInvalid"
case kCFMessagePortTransportError:return "kCFMessagePortTransportError"
case kCFMessagePortBecameInvalidError:return "kCFMessagePortBecameInvalidError"
default:return "unknown"
}
}
}
@ -44,60 +31,50 @@ public final class RxMessagePortClient {
public var timeout = RxMessagePortClient.defaultTimeout
public var queue = DispatchQueue(
label: String(reflecting: RxMessagePortClient.self),
qos: .userInitiated
)
public init() {
}
public func send(
msgid: Int32,
data: Data?,
expectsReply: Bool
) -> Single<Data?> {
return Single.create { single in
Single.create { single in
self.queue.async {
self.portLock.withReadLock {
guard self.portIsValid else {
single(.error(Error.portInvalid))
return
}
let returnDataPtr: UnsafeMutablePointer<Unmanaged<CFData>?>
= UnsafeMutablePointer.allocate(capacity: 1)
defer { returnDataPtr.deallocate() }
let responseCode = CFMessagePortSendRequest(
self.port,
msgid,
data?.cfdata,
self.timeout,
self.timeout,
expectsReply ? CFRunLoopMode.defaultMode.rawValue : nil,
expectsReply ? returnDataPtr : nil
)
guard responseCode == kCFMessagePortSuccess else {
single(.error(Error.send(msgid: msgid, response: responseCode)))
return
}
guard expectsReply else {
single(.success(nil))
return
}
// Upon return, [returnData] contains a CFData object
// containing the reply data. Ownership follows the The Create Rule.
// From: https://developer.apple.com/documentation/corefoundation/1543076-cfmessageportsendrequest
// This means that we have to release the returned CFData.
// Thus, we have to use Unmanaged.takeRetainedValue()
// See also https://www.mikeash.com/pyblog/friday-qa-2017-08-11-swiftunmanaged.html
let data: Data? = returnDataPtr.pointee?.takeRetainedValue().data
single(.success(data))
guard self.portIsValid else {
single(.error(Error.portInvalid))
return
}
let returnDataPtr: UnsafeMutablePointer<Unmanaged<CFData>?>
= UnsafeMutablePointer.allocate(capacity: 1)
defer { returnDataPtr.deallocate() }
let responseCode = CFMessagePortSendRequest(
self.port,
msgid,
data?.cfdata,
self.timeout,
self.timeout,
expectsReply ? CFRunLoopMode.defaultMode.rawValue : nil,
expectsReply ? returnDataPtr : nil
)
guard responseCode == kCFMessagePortSuccess else {
single(.error(Error.send(msgid: msgid, response: responseCode)))
return
}
guard expectsReply else {
single(.success(nil))
return
}
// Upon return, [returnData] contains a CFData object
// containing the reply data. Ownership follows the The Create Rule.
// From: https://developer.apple.com/documentation/corefoundation/1543076-cfmessageportsendrequest
// This means that we have to release the returned CFData.
// Thus, we have to use Unmanaged.takeRetainedValue()
// See also https://www.mikeash.com/pyblog/friday-qa-2017-08-11-swiftunmanaged.html
let data: Data? = returnDataPtr.pointee?.takeRetainedValue().data
single(.success(data))
}
return Disposables.create()
@ -105,34 +82,30 @@ public final class RxMessagePortClient {
}
public func connect(to name: String) -> Completable {
return Completable.create { completable in
Completable.create { completable in
self.queue.async {
self.portLock.withWriteLock {
self.port = CFMessagePortCreateRemote(kCFAllocatorDefault, name.cfstr)
self.port = CFMessagePortCreateRemote(kCFAllocatorDefault, name.cfstr)
if self.port == nil {
completable(.error(Error.clientInit))
return
}
self.portIsValid = true
completable(.completed)
if self.port == nil {
completable(.error(Error.clientInit))
return
}
self.portIsValid = true
completable(.completed)
}
return Disposables.create()
}
}
public func stop() -> Completable {
return Completable.create { completable in
Completable.create { completable in
self.queue.async {
self.portLock.withWriteLock {
self.portIsValid = false
if self.port != nil && CFMessagePortIsValid(self.port) {
CFMessagePortInvalidate(self.port)
}
completable(.completed)
self.portIsValid = false
if self.port != nil && CFMessagePortIsValid(self.port) {
CFMessagePortInvalidate(self.port)
}
completable(.completed)
}
return Disposables.create()
@ -140,8 +113,12 @@ public final class RxMessagePortClient {
}
private var portIsValid = false
private let portLock = ReadersWriterLock()
private var port: CFMessagePort?
private let queue = DispatchQueue(
label: String(reflecting: RxMessagePortClient.self),
qos: .userInitiated
)
}
public final class RxMessagePortServer {
@ -155,29 +132,16 @@ public final class RxMessagePortServer {
}
public var syncReplyBody: SyncReplyBody? {
get {
return self.messageHandler.syncReplyBody
}
set {
self.messageHandler.syncReplyBody = newValue
}
get { self.messageHandler.syncReplyBody }
set { self.messageHandler.syncReplyBody = newValue }
}
public var stream: Observable<Message> {
return self.streamSubject.asObservable()
}
public var stream: Observable<Message> { self.streamSubject.asObservable() }
public var queue = DispatchQueue(
label: String(reflecting: RxMessagePortServer.self),
qos: .userInitiated
)
public init() {
self.messageHandler = MessageHandler(subject: self.streamSubject)
}
public init() { self.messageHandler = MessageHandler(subject: self.streamSubject) }
public func run(as name: String) -> Completable {
return Completable.create { completable in
Completable.create { completable in
self.queue.async {
var localCtx = CFMessagePortContext(
version: 0,
@ -193,8 +157,7 @@ public final class RxMessagePortServer {
{ _, msgid, data, info in
guard let infoPtr = UnsafeRawPointer(info) else { return nil }
let handler = Unmanaged<MessageHandler>
.fromOpaque(infoPtr).takeUnretainedValue()
let handler = Unmanaged<MessageHandler>.fromOpaque(infoPtr).takeUnretainedValue()
return handler.handleMessage(msgId: msgid, cfdata: data)
},
&localCtx,
@ -218,14 +181,12 @@ public final class RxMessagePortServer {
}
public func stop() -> Completable {
return Completable.create { completable in
Completable.create { completable in
self.queue.async {
self.messageHandler.syncReplyBody = nil
self.streamSubject.onCompleted()
if let portRunLoop = self.portRunLoop {
CFRunLoopStop(portRunLoop)
}
if let portRunLoop = self.portRunLoop { CFRunLoopStop(portRunLoop) }
if self.port != nil && CFMessagePortIsValid(self.port) {
CFMessagePortInvalidate(self.port)
@ -242,16 +203,17 @@ public final class RxMessagePortServer {
private var portThread: Thread?
private var portRunLoop: CFRunLoop?
private let queue = DispatchQueue(
label: String(reflecting: RxMessagePortClient.self),
qos: .userInitiated
)
private var messageHandler: MessageHandler
private let streamSubject = PublishSubject<Message>()
private func runServer() {
self.portRunLoop = CFRunLoopGetCurrent()
let runLoopSrc = CFMessagePortCreateRunLoopSource(
kCFAllocatorDefault,
self.port,
0
)
let runLoopSrc = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, self.port, 0)
CFRunLoopAddSource(self.portRunLoop, runLoopSrc, .defaultMode)
CFRunLoopRun()
}
@ -261,9 +223,7 @@ private class MessageHandler {
fileprivate var syncReplyBody: RxMessagePortServer.SyncReplyBody?
fileprivate init(subject: PublishSubject<RxMessagePortServer.Message>) {
self.subject = subject
}
fileprivate init(subject: PublishSubject<RxMessagePortServer.Message>) { self.subject = subject }
fileprivate func handleMessage(
msgId: Int32,
@ -286,21 +246,15 @@ private class MessageHandler {
private extension Data {
var cfdata: CFData {
return self as NSData
}
var cfdata: CFData { self as NSData }
}
private extension CFData {
var data: Data {
return self as NSData as Data
}
var data: Data { self as NSData as Data }
}
private extension String {
var cfstr: CFString {
return self as NSString
}
var cfstr: CFString { self as NSString }
}

View File

@ -48,9 +48,7 @@ public final class RxMsgpackRpc {
Streams `Message.notification`s and `Message.error`s by default.
When `streamResponses` is set to `true`, then also `Message.response`s.
*/
public var stream: Observable<Message> {
return self.streamSubject.asObservable()
}
public var stream: Observable<Message> { self.streamSubject.asObservable() }
/**
When `true`, all messages of type `MessageType.response` are also streamed
@ -60,39 +58,26 @@ public final class RxMsgpackRpc {
*/
public var streamResponses = false
public var queue = DispatchQueue(
label: String(reflecting: RxMsgpackRpc.self),
qos: .userInitiated
)
private var socket: Socket?
private var thread: Thread?
public init() {
}
public func run(at path: String) -> Completable {
return Completable.create { completable in
Completable.create { completable in
self.queue.async {
self.stopLock.withWriteLock {
self.stopped = false
self.stopped = false
do {
try self.socket = Socket.create(
family: .unix,
type: .stream,
proto: .unix
)
try self.socket?.connect(to: path)
self.setUpThreadAndStartReading()
} catch {
self.streamSubject.onError(
Error(msg: "Could not get socket", cause: error)
)
completable(.error(
Error(msg: "Could not get socket at \(path)", cause: error)
))
}
do {
try self.socket = Socket.create(
family: .unix,
type: .stream,
proto: .unix
)
try self.socket?.connect(to: path)
self.setUpThreadAndStartReading()
} catch {
self.streamSubject.onError(
Error(msg: "Could not get socket", cause: error)
)
completable(.error(
Error(msg: "Could not get socket at \(path)", cause: error)
))
}
completable(.completed)
@ -103,14 +88,12 @@ public final class RxMsgpackRpc {
}
public func stop() -> Completable {
return Completable.create { completable in
Completable.create { completable in
self.queue.async {
self.stopLock.withWriteLock {
self.streamSubject.onCompleted()
self.cleanUpAndCloseSocket()
self.streamSubject.onCompleted()
self.cleanUpAndCloseSocket()
completable(.completed)
}
completable(.completed)
}
return Disposables.create()
}
@ -121,14 +104,10 @@ public final class RxMsgpackRpc {
params: [Value],
expectsReturnValue: Bool
) -> Single<Response> {
return Single.create { single in
Single.create { single in
self.queue.async {
let msgid: UInt32 = self.nextMsgidLock.withLock {
let result = self.nextMsgid
self.nextMsgid += 1
return result
}
let msgid = self.nextMsgid
self.nextMsgid += 1
let packed = pack(
[
@ -139,50 +118,42 @@ public final class RxMsgpackRpc {
]
)
self.stopLock.withReadLock {
if self.stopped {
single(.error(Error(msg: "Connection stopped, " +
"but trying to send a request with msg id \(msgid)")))
return
}
if self.stopped {
single(.error(Error(msg: "Connection stopped, " +
"but trying to send a request with msg id \(msgid)")))
return
}
guard let socket = self.socket else {
single(.error(Error(msg: "Socket is invalid, " +
"but trying to send a request with msg id \(msgid)")))
return
}
guard let socket = self.socket else {
single(.error(Error(msg: "Socket is invalid, " +
"but trying to send a request with msg id \(msgid)")))
return
}
if expectsReturnValue {
self.singlesLock.withLock {
self.singles[msgid] = single
}
}
do {
let writtenBytes = try socket.write(from: packed)
if writtenBytes < packed.count {
single(.error(Error(
msg: "(Written) = \(writtenBytes) < \(packed.count) = " +
"(requested) for msg id: \(msgid)"
)))
return
}
} catch {
self.streamSubject.onError(Error(
msg: "Could not write to socket for msg id: " +
"\(msgid)", cause: error))
if expectsReturnValue { self.singles[msgid] = single }
do {
let writtenBytes = try socket.write(from: packed)
if writtenBytes < packed.count {
single(.error(Error(
msg: "Could not write to socket for msg id: " +
"\(msgid)", cause: error)))
msg: "(Written) = \(writtenBytes) < \(packed.count) = " +
"(requested) for msg id: \(msgid)"
)))
return
}
} catch {
self.streamSubject.onError(Error(
msg: "Could not write to socket for msg id: \(msgid)", cause: error))
if !expectsReturnValue {
single(.success(self.nilResponse(with: msgid)))
}
single(.error(Error(
msg: "Could not write to socket for msg id: \(msgid)", cause: error)))
return
}
if !expectsReturnValue {
single(.success(self.nilResponse(with: msgid)))
}
}
@ -190,27 +161,27 @@ public final class RxMsgpackRpc {
}
}
// MARK: - Private
private var nextMsgid: UInt32 = 0
private let nextMsgidLock = NSLock()
private var socket: Socket?
private var thread: Thread?
private let queue = DispatchQueue(
label: String(reflecting: RxMsgpackRpc.self),
qos: .userInitiated
)
private var stopped = true
private let stopLock = ReadersWriterLock()
private var singles: [UInt32: SingleResponseObserver] = [:]
private let streamSubject = PublishSubject<Message>()
private var singles: [UInt32: SingleResponseObserver] = [:]
private let singlesLock = NSLock()
private func nilResponse(with msgid: UInt32) -> Response {
return Response(msgid: msgid, error: .nil, result: .nil)
Response(msgid: msgid, error: .nil, result: .nil)
}
private func cleanUpAndCloseSocket() {
self.singlesLock.withLock {
self.singles.forEach { msgid, single in
single(.success(self.nilResponse(with: msgid)))
}
}
self.singles.forEach { msgid, single in single(.success(self.nilResponse(with: msgid))) }
self.singles.removeAll()
self.stopped = true
@ -219,9 +190,7 @@ public final class RxMsgpackRpc {
private func setUpThreadAndStartReading() {
self.thread = Thread {
guard let socket = self.socket else {
return
}
guard let socket = self.socket else { return }
var readData = Data(capacity: 10240)
repeat {
@ -233,11 +202,8 @@ public final class RxMsgpackRpc {
values.forEach(self.processMessage)
}
} catch let error as Socket.Error {
self.streamSubject.onError(Error(
msg: "Could not read from socket", cause: error)
)
// No need to lock since we are currently trying to open the socket.
self.cleanUpAndCloseSocket()
self.streamSubject.onError(Error(msg: "Could not read from socket", cause: error))
self.queue.async { self.cleanUpAndCloseSocket() }
return
} catch {
self.streamSubject.onNext(
@ -274,31 +240,25 @@ public final class RxMsgpackRpc {
guard array.count == 4 else {
self.streamSubject.onNext(.error(
value: unpacked,
msg: "Got an array of length \(array.count) " +
"for a message type response"
msg: "Got an array of length \(array.count) for a message type response"
))
return
}
guard let msgid64 = array[1].uint64Value else {
self.streamSubject.onNext(.error(
value: unpacked,
msg: "Could not get the msgid"
))
self.streamSubject.onNext(.error(value: unpacked, msg: "Could not get the msgid"))
return
}
self.processResponse(
msgid: UInt32(msgid64),
error: array[2],
result: array[3]
)
self.queue.async {
self.processResponse(msgid: UInt32(msgid64), error: array[2], result: array[3])
}
case .notification:
guard array.count == 3 else {
self.streamSubject.onNext(.error(
value: unpacked,
msg: "Got an array of length \(array.count) " +
"for a message type notification"
msg: "Got an array of length \(array.count) for a message type notification"
))
return
@ -327,34 +287,14 @@ public final class RxMsgpackRpc {
private func processResponse(msgid: UInt32, error: Value, result: Value) {
if self.streamResponses {
self.streamSubject.onNext(.response(
msgid: msgid,
error: error,
result: result
))
self.streamSubject.onNext(.response(msgid: msgid, error: error, result: result))
}
guard let single: SingleResponseObserver = self.singlesLock.withLock({
let s = self.singles[msgid]
self.singles.removeValue(forKey: msgid)
return s
}) else {
return
}
guard let single: SingleResponseObserver = self.singles[msgid] else { return }
single(.success(Response(msgid: msgid, error: error, result: result)))
self.singles.removeValue(forKey: msgid)
}
}
private typealias SingleResponseObserver
= (SingleEvent<RxMsgpackRpc.Response>) -> Void
fileprivate extension NSLocking {
@discardableResult
func withLock<T>(_ body: () -> T) -> T {
self.lock()
defer { self.unlock() }
return body()
}
}
private typealias SingleResponseObserver = (SingleEvent<RxMsgpackRpc.Response>) -> Void

File diff suppressed because it is too large Load Diff

View File

@ -15,87 +15,51 @@ public final class RxNeovimApi {
public struct Buffer: Equatable {
public static func ==(lhs: Buffer, rhs: Buffer) -> Bool {
return lhs.handle == rhs.handle
}
public static func ==(lhs: Buffer, rhs: Buffer) -> Bool { lhs.handle == rhs.handle }
public let handle: Int
public init(_ handle: Int) {
self.handle = handle
}
public init(_ handle: Int) { self.handle = handle }
}
public struct Window: Equatable {
public static func ==(lhs: Window, rhs: Window) -> Bool {
return lhs.handle == rhs.handle
}
public static func ==(lhs: Window, rhs: Window) -> Bool { lhs.handle == rhs.handle }
public let handle: Int
public init(_ handle: Int) {
self.handle = handle
}
public init(_ handle: Int) { self.handle = handle }
}
public struct Tabpage: Equatable {
public static func ==(lhs: Tabpage, rhs: Tabpage) -> Bool {
return lhs.handle == rhs.handle
}
public static func ==(lhs: Tabpage, rhs: Tabpage) -> Bool { lhs.handle == rhs.handle }
public let handle: Int
public init(_ handle: Int) {
self.handle = handle
}
public init(_ handle: Int) { self.handle = handle }
}
public typealias Value = RxMsgpackRpc.Value
public var streamResponses: Bool {
get {
return self.msgpackRpc.streamResponses
}
set {
self.msgpackRpc.streamResponses = newValue
}
get { self.msgpackRpc.streamResponses }
set { self.msgpackRpc.streamResponses = newValue }
}
public var streamRawResponses: Bool {
get {
return self.msgpackRpc.streamResponses
}
set {
self.msgpackRpc.streamResponses = newValue
}
get { self.msgpackRpc.streamResponses }
set { self.msgpackRpc.streamResponses = newValue }
}
public var msgpackRawStream: Observable<RxMsgpackRpc.Message> {
return self.msgpackRpc.stream
}
public var msgpackRawStream: Observable<RxMsgpackRpc.Message> { self.msgpackRpc.stream }
public var queue = DispatchQueue(label: String(reflecting: RxNeovimApi.self), qos: .userInitiated) {
didSet {
self.msgpackRpc.queue = self.queue
}
}
public func run(at path: String) -> Completable { self.msgpackRpc.run(at: path) }
public init() {
self.msgpackRpc.queue = self.queue
}
public func run(at path: String) -> Completable {
return self.msgpackRpc.run(at: path)
}
public func stop() -> Completable {
return self.msgpackRpc.stop()
}
public func stop() -> Completable { self.msgpackRpc.stop() }
public func checkBlocked<T>(_ single: Single<T>) -> Single<T> {
return self
self
.getMode()
.flatMap { dict -> Single<T> in
guard (dict["blocking"]?.boolValue ?? false) == false else {
@ -106,30 +70,24 @@ public final class RxNeovimApi {
}
}
public func rpc(method: String,
params: [RxNeovimApi.Value],
expectsReturnValue: Bool = true) -> Single<RxNeovimApi.Value> {
return self.msgpackRpc
public func rpc(
method: String,
params: [RxNeovimApi.Value],
expectsReturnValue: Bool = true
) -> Single<RxNeovimApi.Value> {
self.msgpackRpc
.request(method: method, params: params, expectsReturnValue: expectsReturnValue)
.map { response -> RxMsgpackRpc.Value in
guard response.error.isNil else {
throw RxNeovimApi.Error(response.error)
}
guard response.error.isNil else { throw RxNeovimApi.Error(response.error) }
return response.result
}
}
private let msgpackRpc = RxMsgpackRpc()
}
fileprivate extension NSLocking {
@discardableResult
func withLock<T>(_ body: () -> T) -> T {
self.lock()
defer { self.unlock() }
return body()
}
private let queue = DispatchQueue(
label: String(reflecting: RxNeovimApi.self),
qos: .userInitiated
)
}

View File

@ -24,6 +24,16 @@ class NvimMsgPackTests: XCTestCase {
try? self.nvim.stop().wait()
}
func testSth() {
nvim
.command(command: "pwd")
.subscribe(onCompleted: { print("completed") }, onError: { print($0) })
// .subscribe(onSuccess: { print($0) })
.disposed(by: self.disposeBag)
sleep(1)
}
func testExample() {
let formatter = DateFormatter()
formatter.dateStyle = .short
@ -33,9 +43,7 @@ class NvimMsgPackTests: XCTestCase {
for i in 0...5 {
nvim
.command(
command: "echo '\(formatter.string(from: now)) \(i)'",
expectsReturnValue: true,
checkBlocked: true
command: "echo '\(formatter.string(from: now)) \(i)'"
)
.subscribe(onCompleted: { print("\(i) handled") })
.disposed(by: dispose)

View File

@ -12,7 +12,6 @@
4B022664224AB1490052362B /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B022662224AB1490052362B /* MainMenu.xib */; };
4B02266F224ACCE70052362B /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B02266D224ACCE10052362B /* RxSwift.framework */; };
4B022670224ACCE70052362B /* RxSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B02266D224ACCE10052362B /* RxSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4B022685224ACFCB0052362B /* ReadersWriterLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B022683224ACFCB0052362B /* ReadersWriterLock.swift */; };
4B022686224ACFCB0052362B /* RxMessagePort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B022684224ACFCB0052362B /* RxMessagePort.swift */; };
4B0226A8224B58DB0052362B /* RxMsgpackRpcTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0226A7224B58DB0052362B /* RxMsgpackRpcTests.swift */; };
4B0226AD224B59010052362B /* Socket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B02268E224B56F10052362B /* Socket.framework */; };
@ -21,14 +20,12 @@
4B0226B1224B5A180052362B /* MessagePack.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B02268D224B56F10052362B /* MessagePack.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4B0226B2224B5A180052362B /* Socket.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B02268E224B56F10052362B /* Socket.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4B0226B3224B5A180052362B /* RxSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B02266D224ACCE10052362B /* RxSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4B0226B4224B5A310052362B /* ReadersWriterLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B022683224ACFCB0052362B /* ReadersWriterLock.swift */; };
4B0226B5224B5A310052362B /* RxMsgpackRpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BDD5380FFB00320A20C9 /* RxMsgpackRpc.swift */; };
4BB1EF8C224B62BB00A5CD5A /* RxSwiftCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB1EF8B224B625700A5CD5A /* RxSwiftCommons.swift */; };
4BB1EF98224B66AB00A5CD5A /* RxNeovimApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB1EF97224B66AB00A5CD5A /* RxNeovimApiTests.swift */; };
4BB1EF9D224B66BB00A5CD5A /* RxNeovimApi.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB1EF90224B662E00A5CD5A /* RxNeovimApi.generated.swift */; };
4BB1EF9E224B66BB00A5CD5A /* RxNeovimApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB1EF8E224B662E00A5CD5A /* RxNeovimApi.swift */; };
4BB1EFA0224B66BB00A5CD5A /* RxSwiftCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB1EF8B224B625700A5CD5A /* RxSwiftCommons.swift */; };
4BB1EFA1224B66BB00A5CD5A /* ReadersWriterLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B022683224ACFCB0052362B /* ReadersWriterLock.swift */; };
4BB1EFA2224B66BB00A5CD5A /* RxMsgpackRpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BDD5380FFB00320A20C9 /* RxMsgpackRpc.swift */; };
4BB1EFA3224B66C700A5CD5A /* MessagePack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B02268D224B56F10052362B /* MessagePack.framework */; };
4BB1EFA4224B66C700A5CD5A /* Socket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B02268E224B56F10052362B /* Socket.framework */; };
@ -84,7 +81,6 @@
4B022663224AB1490052362B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
4B022665224AB1490052362B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4B02266D224ACCE10052362B /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = ../Carthage/Build/Mac/RxSwift.framework; sourceTree = "<group>"; };
4B022683224ACFCB0052362B /* ReadersWriterLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadersWriterLock.swift; sourceTree = "<group>"; };
4B022684224ACFCB0052362B /* RxMessagePort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxMessagePort.swift; sourceTree = "<group>"; };
4B02268D224B56F10052362B /* MessagePack.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessagePack.framework; path = ../Carthage/Build/Mac/MessagePack.framework; sourceTree = "<group>"; };
4B02268E224B56F10052362B /* Socket.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Socket.framework; path = ../Carthage/Build/Mac/Socket.framework; sourceTree = "<group>"; };
@ -136,7 +132,6 @@
children = (
4BB1EF8D224B660E00A5CD5A /* RxNeovimApi */,
4BB1EF8B224B625700A5CD5A /* RxSwiftCommons.swift */,
4B022683224ACFCB0052362B /* ReadersWriterLock.swift */,
4B022684224ACFCB0052362B /* RxMessagePort.swift */,
4B02265D224AB1490052362B /* RxMessagePortDemo */,
4B0226A6224B58DB0052362B /* RxMsgpackRpcTests */,
@ -335,7 +330,6 @@
buildActionMask = 2147483647;
files = (
4B022686224ACFCB0052362B /* RxMessagePort.swift in Sources */,
4B022685224ACFCB0052362B /* ReadersWriterLock.swift in Sources */,
4B02265F224AB1490052362B /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -345,7 +339,6 @@
buildActionMask = 2147483647;
files = (
4BB1EF8C224B62BB00A5CD5A /* RxSwiftCommons.swift in Sources */,
4B0226B4224B5A310052362B /* ReadersWriterLock.swift in Sources */,
4B0226B5224B5A310052362B /* RxMsgpackRpc.swift in Sources */,
4B0226A8224B58DB0052362B /* RxMsgpackRpcTests.swift in Sources */,
);
@ -358,7 +351,6 @@
4BB1EF9D224B66BB00A5CD5A /* RxNeovimApi.generated.swift in Sources */,
4BB1EF9E224B66BB00A5CD5A /* RxNeovimApi.swift in Sources */,
4BB1EFA0224B66BB00A5CD5A /* RxSwiftCommons.swift in Sources */,
4BB1EFA1224B66BB00A5CD5A /* ReadersWriterLock.swift in Sources */,
4BB1EFA2224B66BB00A5CD5A /* RxMsgpackRpc.swift in Sources */,
4BB1EF98224B66AB00A5CD5A /* RxNeovimApiTests.swift in Sources */,
);

View File

@ -12,15 +12,14 @@ import io
void_func_template = Template('''\
func ${func_name}(${args}
expectsReturnValue: Bool = true,
checkBlocked: Bool = true
expectsReturnValue: Bool = false
) -> Completable {
let params: [RxNeovimApi.Value] = [
${params}
]
if expectsReturnValue && checkBlocked {
if expectsReturnValue {
return self
.checkBlocked(
self.rpc(method: "${nvim_func_name}", params: params, expectsReturnValue: expectsReturnValue)
@ -55,7 +54,7 @@ get_mode_func_template = Template('''\
func_template = Template('''\
func ${func_name}(${args}
checkBlocked: Bool = true
errWhenBlocked: Bool = true
) -> Single<${result_type}> {
let params: [RxNeovimApi.Value] = [
@ -70,7 +69,7 @@ func_template = Template('''\
return result
}
if checkBlocked {
if errWhenBlocked {
return self
.checkBlocked(
self.rpc(method: "${nvim_func_name}", params: params, expectsReturnValue: true)

View File

@ -1920,7 +1920,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 335;
CURRENT_PROJECT_VERSION = 336;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@ -1981,7 +1981,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 335;
CURRENT_PROJECT_VERSION = 336;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_NS_ASSERTIONS = NO;

View File

@ -1224,7 +1224,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.30.0</string>
<string>SNAPSHOT-336</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@ -1241,7 +1241,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>335</string>
<string>336</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSMinimumSystemVersion</key>

View File

@ -152,6 +152,7 @@ class OpenQuicklyWindow: NSObject,
private func reset() {
self.scanToken = Token()
self.currentSearchService?.stopScanScore()
self.currentSearchService = nil
self.endProgress()
self.unsortedScoredUrls.removeAll()

View File

@ -15,10 +15,10 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>0.30.0</string>
<string>SNAPSHOT-336</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>335</string>
<string>336</string>
</dict>
</plist>

View File

@ -7,37 +7,23 @@
<description>Most recent changes with links to updates for VimR.</description>
<language>en</language>
<item>
<title>v0.30.0-335</title>
<title>SNAPSHOT-336</title>
<description><![CDATA[
<ul>
<li>Improve Open Quickly<ul>
<li>Use <a href="https://github.com/ggreer/the_silver_searcher">The Silver Searcher</a>'s ignore mechanism</li>
<li>Use <a href="https://github.com/MaskRay/ccls">ccls</a>' fuzzy search</li>
</ul>
</li>
<li>GH-730: Add "Close Window" menu item, which closes all tabs (and the VimR window).</li>
<li>GH-768: Bugfix: coc.nvim does not work.</li>
<li>Bugfix: VimR hangs when there are windows in which nvim is waiting for user input.</li>
<li>Bugfix: Forward search in Markdown preview does not work.</li>
<li>Bugfix: "Open Quickly" result rows are not dark mode compatible.</li>
<li>Bugfix: Enter without selecting a result in the "Open Quickly" window results in a crash.</li>
<li>Dependencies updates:<ul>
<li>IBM-Swift/BlueSocket 1.0.52</li>
<li>elegantchaos/DictionaryCoding 1.0.7</li>
</ul>
</li>
<li><em>Note that SNAPSHOT builds are prone to be very unstable.</em></li>
<li>Improve handling of file system changes for the file browser</li>
</ul>
]]></description>
<releaseNotesLink>
https://github.com/qvacua/vimr/releases/tag/v0.30.0-335
https://github.com/qvacua/vimr/releases/tag/snapshot/336
</releaseNotesLink>
<pubDate>2020-01-24T23:40:24.018858</pubDate>
<pubDate>2020-01-26T22:17:08.186659</pubDate>
<minimumSystemVersion>10.12.0</minimumSystemVersion>
<enclosure url="https://github.com/qvacua/vimr/releases/download/v0.30.0-335/VimR-v0.30.0-335.tar.bz2"
sparkle:version="335"
sparkle:shortVersionString="0.30.0"
sparkle:dsaSignature="MCwCFBruv+r11RKh6fPQq1aP+bJoIabKAhRDGo9Q6NNCefyQnpsBhm44fKRJ3w=="
length="14656304"
<enclosure url="https://github.com/qvacua/vimr/releases/download/snapshot/336/VimR-SNAPSHOT-336.tar.bz2"
sparkle:version="336"
sparkle:shortVersionString="SNAPSHOT-336"
sparkle:dsaSignature="MCwCFBcZO/2EKNKIme3I2mOMZ7m+BEmdAhRgZdHZ9L7GBbda0jbX0XcSu8OCwg=="
length="14699218"
type="application/octet-stream"/>
</item>
</channel>