1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-25 06:43:24 +03:00

Merge remote-tracking branch 'origin/pr/1015' into stock-nvim

Conflicts:
	.gitignore
	NvimServer~HEAD
	NvimView/Sources/NvimView/NvimView.swift
	NvimView/Sources/NvimView/UiBridge.swift
	NvimView/Support/NvimViewSupport.xcodeproj/project.pbxproj
	VimR.xcworkspace/xcshareddata/swiftpm/Package.resolved
This commit is contained in:
Tae Won Ha 2023-11-08 12:32:33 +01:00
commit ffb3423b0f
No known key found for this signature in database
GPG Key ID: E40743465B5B8B44
55 changed files with 1460 additions and 1027 deletions

4
.gitignore vendored
View File

@ -181,3 +181,7 @@ Temporary Items
tags.*
.vscode/
/RxPack/.build
/Workspace/.swiftpm
.swiftpm
/NvimServer/.build

6
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "NvimServer"]
path = NvimServer
url = git@github.com:qvacua/neovim.git
[submodule "Neovim"]
path = Neovim
url = git@github.com:georgeharker/vimr-neovim.git

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@ -202,7 +202,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@ -257,7 +257,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SWIFT_COMPILATION_MODE = wholemodule;

1
Neovim Submodule

@ -0,0 +1 @@
Subproject commit 3dcf6880ad65d15495dce18211e72a41e46f502c

@ -1 +0,0 @@
Subproject commit 8ecd4b24d78ebd4f8fc1394f5fc89d65f025b04d

3
NvimServer/NvimServer/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.deps
third-party

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,7 @@
{
"deploymentTarget": "10.15",
"gettext": {
"arm64BottleTag": "arm64_ventura",
"x86_64BottleTag": "ventura"
}
}

View File

@ -0,0 +1 @@
../../NvimServerTypes/Sources/include/NvimServerTypes.h

View File

@ -0,0 +1 @@
../../../Neovim/src/nvim/main.c

1
NvimServer/NvimServer/bin/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea

View File

@ -0,0 +1,86 @@
## How to develop
First, clean everything
```
$ ./bin/clean_all.sh
```
Then, build `libnvim` once with dependencies
```
$ target=x86_64 build_deps=true ./bin/build_libnvim.sh
```
After editing the code of neovim or NvimServer, you build NvimServer in Xcode or by executing
the following:
```
$ target="${ARCH}" build_deps=false build_dir="${PROJECT_ROOT}/NvimServer/build" \
./bin/build_nvimserver.sh
```
where `${ARCH}` is either `arm64` or `x86_64`.
We use `${PROJECT_ROOT}/NvimServer/build` as the NvimServer target assumes that location.
## How to release
```
$ ./bin/build_release.sh
```
The resulting package will be in `${PROJECT_ROOT}/NvimServer/build/NvimServer.tar.bz2`.
## Individual steps
In the following the `target` variable can be either `x86_64` or `arm64`.
### How to build `libintl`
```
$ ./bin/build_deps.sh
```
which will result in
```
${PROJECT_ROOT}
NvimServer
third-party
lib
liba
libb
...
include
a.h
b.h
...
x86_64
lib
liba
libb
include
a.h
b.h
```
Files, e.g. `lib` and `include`, in `${PROJECT_ROOT}/NvimServer/third-party` are used to build
`libnvim` and NvimServer.
### How to build `libnvim`
```
$ build_deps=true ./bin/build_libnvim.sh
```
When `build_deps` is `true`, then the `build_deps.sh` is executed. The resuling library will be
located in `/build/lib/libnvim.a`.
### how to build NvimServer
```
$ build_dir="${some_dir}" build_libnvim=true build_deps=true ./bin/build_nvimserver.sh
```
The `build_libnvim.sh` script is executed automatically with the given parameters. The resulting
binary will be located in `${some_dir}`.

View File

@ -0,0 +1,56 @@
#!/bin/bash
set -Eeuo pipefail
readonly clean=${clean:?"true or false"}
build_libnvim() {
local -r deployment_target=$1
# Brew's gettext does not get sym-linked to PATH
export PATH="/opt/homebrew/opt/gettext/bin:/usr/local/opt/gettext/bin:${PATH}"
macos_flags="-DCMAKE_OSX_DEPLOYMENT_TARGET=${deployment_target} -DCMAKE_OSX_ARCHITECTURES=arm64\;x86_64"
#macos_flags="-DCMAKE_OSX_DEPLOYMENT_TARGET=${deployment_target} -DCMAKE_OSX_ARCHITECTURES=arm64"
pushd ../Neovim
# W/o setting MACOSX_DEPLOYMENT_TARGET, the dependencies have min. macOS set to the macOS you're on.
make \
CMAKE_BUILD_TYPE=Release \
SDKROOT="$(xcrun --show-sdk-path)" \
MACOSX_DEPLOYMENT_TARGET="${deployment_target}" \
CMAKE_EXTRA_FLAGS="" \
CMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
DEPS_CMAKE_FLAGS="${macos_flags}" \
libnvim nvim
popd
}
main() {
# This script is located in /NvimServer/bin and we have to go to /
echo "$(dirname "${BASH_SOURCE[0]}")/../../"
pushd "$(dirname "${BASH_SOURCE[0]}")/../../" >/dev/null
echo "### Building libnvim"
local deployment_target
deployment_target=$(jq -r .deploymentTarget ./NvimServer/Resources/buildInfo.json)
readonly deployment_target
if [[ "${clean}" == true ]]; then
pushd ../Neovim
make distclean
popd
./NvimServer/bin/prepare_libintl.sh
fi
build_libnvim "${deployment_target}"
popd >/dev/null
echo "### Built libnvim"
}
main

View File

@ -0,0 +1,27 @@
#!/bin/bash
set -Eeuo pipefail
declare -r -x clean=${clean:?"if true, will clean libnvim and nvimserver"}
readonly build_libnvim=${build_libnvim:?"true or false"}
readonly build_dir=${build_dir:-"./.build"}
main() {
echo "### Building NvimServer"
# This script is located in /NvimServer/bin and we have to go to /
pushd "$(dirname "${BASH_SOURCE[0]}")/../.." >/dev/null
if [[ "${clean}" == true ]]; then
rm -rf "${build_dir}"
fi
if [[ "${build_libnvim}" == true ]]; then
./NvimServer/bin/build_libnvim.sh
fi
swift build --arch arm64 --arch x86_64 -c release --product NvimServer
#swift build --arch arm64 -c release --product NvimServer
popd >/dev/null
echo "### Built NvimServer"
}
main

View File

@ -0,0 +1,42 @@
#!/bin/bash
set -Eeuo pipefail
readonly nvim_install_path=${nvim_install_path:?"where to install temp nvim"}
build_runtime() {
pushd ../Neovim
local -r deployment_target=$1
echo "#### runtime in ${nvim_install_path}"
echo "### Building nvim to get the complete runtime"
make \
SDKROOT="$(xcrun --show-sdk-path)" \
MACOSX_DEPLOYMENT_TARGET="${deployment_target}" \
CMAKE_EXTRA_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=${deployment_target} -DCUSTOM_UI=0 -DCMAKE_INSTALL_PREFIX=${nvim_install_path}" \
DEPS_CMAKE_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=${deployment_target}" \
CMAKE_BUILD_TYPE="Release" \
install
echo "#### runtime is installed at ${nvim_install_path}/share/nvim/runtime"
popd
}
main() {
# This script is located in /NvimServer/bin and we have to go to /
pushd "$(dirname "${BASH_SOURCE[0]}")/../.." >/dev/null
echo "### Building runtime"
local deployment_target
deployment_target=$(jq -r .deploymentTarget ./NvimServer/Resources/buildInfo.json)
readonly deployment_target
build_runtime "${deployment_target}"
popd >/dev/null
echo "### Built runtime"
}
main

View File

@ -0,0 +1,18 @@
#!/bin/bash
set -Eeuo pipefail
readonly clean_deps=${clean_deps:-true}
pushd "$(dirname "${BASH_SOURCE[0]}")/../.." >/dev/null
pushd ../Neovim
rm -rf ./build
rm -rf ./.deps
make distclean
popd
if [[ "${clean_deps}" == true ]]; then
rm -rf ./NvimServer/build
fi
popd >/dev/null

View File

@ -0,0 +1,68 @@
#!/bin/bash
set -Eeuo pipefail
shopt -s extglob
main() {
# This script is located in /NvimServer/bin and we have to go to /
pushd "$(dirname "${BASH_SOURCE[0]}")/../.." >/dev/null
rm -rf ./NvimServer/third-party
mkdir -p NvimServer/third-party
local arm64_bottle
arm64_bottle=$(jq -r .gettext.arm64BottleTag ./NvimServer/Resources/buildInfo.json)
readonly arm64_bottle
local x86_64_bottle
x86_64_bottle=$(jq -r .gettext.x86_64BottleTag ./NvimServer/Resources/buildInfo.json)
readonly x86_64_bottle
pushd ./NvimServer/third-party >/dev/null
brew fetch --bottle-tag="${arm64_bottle}" gettext
brew fetch --bottle-tag="${x86_64_bottle}" gettext
brew fetch --bottle-tag="${arm64_bottle}" lua
brew fetch --bottle-tag="${x86_64_bottle}" lua
local version; version=$(brew info gettext --json | jq -r ".[0].versions.stable"); readonly version
echo "### gettext version ${version}"
local lua_version; lua_version=$(brew info lua --json | jq -r ".[0].versions.stable"); readonly version
echo "### gettext lua_version ${lua_version}"
local temp_dir; temp_dir="$(mktemp -d)"; readonly temp_dir
echo "${temp_dir}"
pushd "${temp_dir}" >/dev/null
mkdir "${arm64_bottle}"
pushd "${arm64_bottle}" >/dev/null
tar xf "$(brew --cache)"/**/*--gettext--+([0-9.])${arm64_bottle}*.tar.gz
tar xf "$(brew --cache)"/**/*--lua--+([0-9.])${arm64_bottle}*.tar.gz
popd >/dev/null
mkdir "${x86_64_bottle}"
pushd "${x86_64_bottle}" >/dev/null
tar xf "$(brew --cache)"/**/*--gettext--+([0-9.])${x86_64_bottle}*.tar.gz
tar xf "$(brew --cache)"/**/*--lua--+([0-9.])${x86_64_bottle}*.tar.gz
popd >/dev/null
mkdir universal
cp -r "${arm64_bottle}/gettext/${version}/include" ./universal/
mkdir universal/lib
lipo "${arm64_bottle}/gettext/${version}/lib/libintl.a" "${x86_64_bottle}/gettext/${version}/lib/libintl.a" -create -output ./universal/lib/libintl.a
mkdir universal_lua
cp -r "${arm64_bottle}/lua/${lua_version}/include" ./universal_lua/
mkdir universal_lua/lib
lipo "${arm64_bottle}/lua/${lua_version}/lib/liblua.a" "${x86_64_bottle}/lua/${lua_version}/lib/liblua.a" -create -output ./universal_lua/lib/liblua.a
popd >/dev/null
mv "${temp_dir}/universal" gettext
mv "${temp_dir}/universal_lua" lua
rm -rf "${temp_dir}"
popd >/dev/null
popd >/dev/null
}
main

View File

@ -0,0 +1 @@
../../Neovim

View File

@ -0,0 +1,76 @@
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
#ifndef NVIMSERVER_SHARED_TYPES_H
#define NVIMSERVER_SHARED_TYPES_H
#include <CoreFoundation/CoreFoundation.h>
#define NSInteger long
#define NSUInteger unsigned long
typedef CF_OPTIONS(NSUInteger, FontTrait) {
FontTraitNone = 0,
FontTraitItalic = (1 << 0),
FontTraitBold = (1 << 1),
FontTraitUnderline = (1 << 2),
FontTraitUndercurl = (1 << 3)
};
typedef CF_ENUM(NSInteger, RenderDataType) {
RenderDataTypeRawLine,
RenderDataTypeGoto,
RenderDataTypeScroll,
};
typedef CF_ENUM(NSInteger, NvimServerMsgId) {
NvimServerMsgIdServerReady = 0,
NvimServerMsgIdNvimReady,
NvimServerMsgIdResize,
NvimServerMsgIdClear,
NvimServerMsgIdSetMenu,
NvimServerMsgIdBusyStart,
NvimServerMsgIdBusyStop,
NvimServerMsgIdModeChange,
NvimServerMsgIdModeInfoSet,
NvimServerMsgIdBell,
NvimServerMsgIdVisualBell,
NvimServerMsgIdFlush,
NvimServerMsgIdHighlightAttrs,
NvimServerMsgIdSetTitle,
NvimServerMsgIdStop,
NvimServerMsgIdOptionSet,
NvimServerMsgIdEvent,
NvimServerMsgIdDirtyStatusChanged,
NvimServerMsgIdCwdChanged,
NvimServerMsgIdColorSchemeChanged,
NvimServerMsgIdDefaultColorsChanged,
NvimServerMsgIdAutoCommandEvent,
NvimServerMsgIdRpcEventSubscribed,
NvimServerMsgIdFatalError,
NvimServerMsgIdDebug1,
};
typedef CF_ENUM(NSInteger, NvimServerFatalErrorCode) {
NvimServerFatalErrorCodeLocalPort = 1,
NvimServerFatalErrorCodeRemotePort,
};
typedef CF_ENUM(NSInteger, NvimBridgeMsgId) {
NvimBridgeMsgIdAgentReady = 0,
NvimBridgeMsgIdReadyForRpcEvents,
NvimBridgeMsgIdDeleteInput,
NvimBridgeMsgIdResize,
NvimBridgeMsgIdScroll,
NvimBridgeMsgIdFocusGained,
NvimBridgeMsgIdDebug1,
};
#endif // NVIMSERVER_SHARED_TYPES_H

View File

@ -0,0 +1,6 @@
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
// SwiftPM requires at least one c source file, which can be empty.

58
NvimServer/Package.swift Normal file
View File

@ -0,0 +1,58 @@
// swift-tools-version:5.7
import PackageDescription
let package = Package(
name: "NvimServer",
platforms: [.macOS(.v13)],
products: [
.library(name: "NvimServerTypes", targets: ["NvimServerTypes"]),
],
dependencies: [],
targets: [
.target(name: "NvimServerTypes", dependencies: [], path: "NvimServerTypes"),
.executableTarget(
name: "NvimServer",
dependencies: [],
path: "NvimServer/Sources",
cSettings: [
// Otherwise we get typedef redefinition error due to double definition of Boolean
.unsafeFlags(["-fno-modules"]),
.define("INCLUDE_GENERATED_DECLARATIONS", to: "1"),
// The target folder is the working directory.
.headerSearchPath("../../NvimServer/neovim/src"),
.headerSearchPath("../../NvimServer/neovim/build/include"),
.headerSearchPath("../../NvimServer/neovim/.deps/usr/include"),
.headerSearchPath("../../NvimServer/neovim/build/cmake.config"),
.headerSearchPath("../../NvimServer/neovim/build/src/nvim/auto/"),
.headerSearchPath("../../NvimServer/third-party/gettext/include"),
.headerSearchPath("../../NvimServer/third-party/lua/include/lua"),
],
linkerSettings: [
.linkedFramework("CoreServices"),
.linkedFramework("CoreFoundation"),
.linkedLibrary("util"),
.linkedLibrary("m"),
.linkedLibrary("dl"),
.linkedLibrary("pthread"),
.linkedLibrary("iconv"),
.unsafeFlags([
// These paths seem to depend on where swift build is executed. Xcode does it in the
// folder where Package.swift is located.
"../neovim/build/lib/libnvim.a",
"../neovim/.deps/usr/lib/libmsgpack-c.a",
"../neovim/.deps/usr/lib/libluv.a",
"../neovim/.deps/usr/lib/liblpeg.a",
"../neovim/.deps/usr/lib/libtermkey.a",
"../neovim/.deps/usr/lib/libuv.a",
"../neovim/.deps/usr/lib/libunibilium.a",
"../neovim/.deps/usr/lib/libvterm.a",
"../neovim/.deps/usr/lib/libluajit-5.1.a",
"../neovim/.deps/usr/lib/libtree-sitter.a",
"NvimServer/third-party/gettext/lib/libintl.a",
]),
]
),
],
cLanguageStandard: .gnu99
)

4
NvimServer/README.md Normal file
View File

@ -0,0 +1,4 @@
# NvimServerTypes
This is a package to provider some types of NvimServer that are used in NvimView.
It consists of just one header file. Since SwiftPM requires at least a C file, we included an empty C file.

View File

@ -1,130 +1,132 @@
// Auto generated for nvim v0.8.2
// Auto generated for nvim v0.9.0-dev
// See bin/generate_autocmds.py
enum NvimAutoCommandEvent: Int {
enum NvimAutoCommandEvent: String {
case bufadd = 0
case bufdelete = 1
case bufenter = 2
case buffilepost = 3
case buffilepre = 4
case bufhidden = 5
case bufleave = 6
case bufmodifiedset = 7
case bufnew = 8
case bufnewfile = 9
case bufreadcmd = 10
case bufreadpost = 11
case bufreadpre = 12
case bufunload = 13
case bufwinenter = 14
case bufwinleave = 15
case bufwipeout = 16
case bufwritecmd = 17
case bufwritepost = 18
case bufwritepre = 19
case chaninfo = 20
case chanopen = 21
case cmdundefined = 22
case cmdwinenter = 23
case cmdwinleave = 24
case cmdlinechanged = 25
case cmdlineenter = 26
case cmdlineleave = 27
case colorscheme = 28
case colorschemepre = 29
case completechanged = 30
case completedone = 31
case completedonepre = 32
case cursorhold = 33
case cursorholdi = 34
case cursormoved = 35
case cursormovedi = 36
case diagnosticchanged = 37
case diffupdated = 38
case dirchanged = 39
case dirchangedpre = 40
case encodingchanged = 41
case exitpre = 42
case fileappendcmd = 43
case fileappendpost = 44
case fileappendpre = 45
case filechangedro = 46
case filechangedshell = 47
case filechangedshellpost = 48
case filereadcmd = 49
case filereadpost = 50
case filereadpre = 51
case filetype = 52
case filewritecmd = 53
case filewritepost = 54
case filewritepre = 55
case filterreadpost = 56
case filterreadpre = 57
case filterwritepost = 58
case filterwritepre = 59
case focusgained = 60
case focuslost = 61
case funcundefined = 62
case guienter = 63
case guifailed = 64
case insertchange = 65
case insertcharpre = 66
case insertenter = 67
case insertleave = 68
case insertleavepre = 69
case lspattach = 70
case lspdetach = 71
case menupopup = 72
case modechanged = 73
case optionset = 74
case quickfixcmdpost = 75
case quickfixcmdpre = 76
case quitpre = 77
case recordingenter = 78
case recordingleave = 79
case remotereply = 80
case searchwrapped = 81
case sessionloadpost = 82
case shellcmdpost = 83
case shellfilterpost = 84
case signal = 85
case sourcecmd = 86
case sourcepost = 87
case sourcepre = 88
case spellfilemissing = 89
case stdinreadpost = 90
case stdinreadpre = 91
case swapexists = 92
case syntax = 93
case tabclosed = 94
case tabenter = 95
case tableave = 96
case tabnew = 97
case tabnewentered = 98
case termchanged = 99
case termclose = 100
case termenter = 101
case termleave = 102
case termopen = 103
case termresponse = 104
case textchanged = 105
case textchangedi = 106
case textchangedp = 107
case textyankpost = 108
case uienter = 109
case uileave = 110
case user = 111
case vimenter = 112
case vimleave = 113
case vimleavepre = 114
case vimresized = 115
case vimresume = 116
case vimsuspend = 117
case winclosed = 118
case winenter = 119
case winleave = 120
case winnew = 121
case winscrolled = 122
case bufadd = "bufadd"
case bufdelete = "bufdelete"
case bufenter = "bufenter"
case buffilepost = "buffilepost"
case buffilepre = "buffilepre"
case bufhidden = "bufhidden"
case bufleave = "bufleave"
case bufmodifiedset = "bufmodifiedset"
case bufnew = "bufnew"
case bufnewfile = "bufnewfile"
case bufreadcmd = "bufreadcmd"
case bufreadpost = "bufreadpost"
case bufreadpre = "bufreadpre"
case bufunload = "bufunload"
case bufwinenter = "bufwinenter"
case bufwinleave = "bufwinleave"
case bufwipeout = "bufwipeout"
case bufwritecmd = "bufwritecmd"
case bufwritepost = "bufwritepost"
case bufwritepre = "bufwritepre"
case chaninfo = "chaninfo"
case chanopen = "chanopen"
case cmdundefined = "cmdundefined"
case cmdwinenter = "cmdwinenter"
case cmdwinleave = "cmdwinleave"
case cmdlinechanged = "cmdlinechanged"
case cmdlineenter = "cmdlineenter"
case cmdlineleave = "cmdlineleave"
case colorscheme = "colorscheme"
case colorschemepre = "colorschemepre"
case completechanged = "completechanged"
case completedone = "completedone"
case completedonepre = "completedonepre"
case cursorhold = "cursorhold"
case cursorholdi = "cursorholdi"
case cursormoved = "cursormoved"
case cursormovedi = "cursormovedi"
case diagnosticchanged = "diagnosticchanged"
case diffupdated = "diffupdated"
case dirchanged = "dirchanged"
case dirchangedpre = "dirchangedpre"
case encodingchanged = "encodingchanged"
case exitpre = "exitpre"
case fileappendcmd = "fileappendcmd"
case fileappendpost = "fileappendpost"
case fileappendpre = "fileappendpre"
case filechangedro = "filechangedro"
case filechangedshell = "filechangedshell"
case filechangedshellpost = "filechangedshellpost"
case filereadcmd = "filereadcmd"
case filereadpost = "filereadpost"
case filereadpre = "filereadpre"
case filetype = "filetype"
case filewritecmd = "filewritecmd"
case filewritepost = "filewritepost"
case filewritepre = "filewritepre"
case filterreadpost = "filterreadpost"
case filterreadpre = "filterreadpre"
case filterwritepost = "filterwritepost"
case filterwritepre = "filterwritepre"
case focusgained = "focusgained"
case focuslost = "focuslost"
case funcundefined = "funcundefined"
case guienter = "guienter"
case guifailed = "guifailed"
case insertchange = "insertchange"
case insertcharpre = "insertcharpre"
case insertenter = "insertenter"
case insertleave = "insertleave"
case insertleavepre = "insertleavepre"
case lspattach = "lspattach"
case lspdetach = "lspdetach"
case menupopup = "menupopup"
case modechanged = "modechanged"
case optionset = "optionset"
case quickfixcmdpost = "quickfixcmdpost"
case quickfixcmdpre = "quickfixcmdpre"
case quitpre = "quitpre"
case recordingenter = "recordingenter"
case recordingleave = "recordingleave"
case remotereply = "remotereply"
case searchwrapped = "searchwrapped"
case sessionloadpost = "sessionloadpost"
case shellcmdpost = "shellcmdpost"
case shellfilterpost = "shellfilterpost"
case signal = "signal"
case sourcecmd = "sourcecmd"
case sourcepost = "sourcepost"
case sourcepre = "sourcepre"
case spellfilemissing = "spellfilemissing"
case stdinreadpost = "stdinreadpost"
case stdinreadpre = "stdinreadpre"
case swapexists = "swapexists"
case syntax = "syntax"
case tabclosed = "tabclosed"
case tabenter = "tabenter"
case tableave = "tableave"
case tabnew = "tabnew"
case tabnewentered = "tabnewentered"
case termchanged = "termchanged"
case termclose = "termclose"
case termenter = "termenter"
case termleave = "termleave"
case termopen = "termopen"
case termresponse = "termresponse"
case textchanged = "textchanged"
case textchangedi = "textchangedi"
case textchangedp = "textchangedp"
case textchangedt = "textchangedt"
case textyankpost = "textyankpost"
case uienter = "uienter"
case uileave = "uileave"
case user = "user"
case vimenter = "vimenter"
case vimleave = "vimleave"
case vimleavepre = "vimleavepre"
case vimresized = "vimresized"
case vimresume = "vimresume"
case vimsuspend = "vimsuspend"
case winclosed = "winclosed"
case winenter = "winenter"
case winleave = "winleave"
case winnew = "winnew"
case winresized = "winresized"
case winscrolled = "winscrolled"
}

View File

@ -1,27 +1,26 @@
// Auto generated for nvim v0.8.2
// Auto generated for nvim v0.9.0-dev
// See bin/generate_cursor_shape.py
public enum CursorModeShape: UInt {
public enum CursorModeShape: String {
case normal = 0
case visual = 1
case insert = 2
case replace = 3
case cmdlineNormal = 4
case cmdlineInsert = 5
case cmdlineReplace = 6
case operatorPending = 7
case visualExclusive = 8
case onCmdline = 9
case onStatusLine = 10
case draggingStatusLine = 11
case onVerticalSepLine = 12
case draggingVerticalSepLine = 13
case more = 14
case moreLastLine = 15
case showingMatchingParen = 16
case termFocus = 17
case count = 18
case normal = "normal"
case visual = "visual"
case insert = "insert"
case replace = "replace"
case cmdlineNormal = "cmdlineNormal"
case cmdlineInsert = "cmdlineInsert"
case cmdlineReplace = "cmdlineReplace"
case operatorPending = "operatorPending"
case visualExclusive = "visualExclusive"
case onCmdline = "onCmdline"
case onStatusLine = "onStatusLine"
case draggingStatusLine = "draggingStatusLine"
case onVerticalSepLine = "onVerticalSepLine"
case draggingVerticalSepLine = "draggingVerticalSepLine"
case more = "more"
case moreLastLine = "moreLastLine"
case showingMatchingParen = "showingMatchingParen"
case termFocus = "termFocus"
case count = "count"
}

View File

@ -38,7 +38,18 @@ public extension NvimView {
}
func hasDirtyBuffers() -> Single<Bool> {
self.api.getDirtyStatus()
self.api
.execLua(code: """
return vim.fn.getbufinfo({"bufmodified": v:true})
""", args: [])
.map { result -> Bool in
guard let info_array = result.arrayValue
else {
throw RxNeovimApi.Error
.exception(message: "Could not convert values into info array.")
}
return info_array.count > 0
}
}
func waitTillNvimExits() {
@ -196,7 +207,17 @@ public extension NvimView {
func vimOutput(of command: String) -> Single<String> {
self.api
.exec(src: command, output: true)
.exec2(src: command, opts:["output": true])
.map({
retval in
guard let output_value = retval["output"] ?? retval["output"],
let output = output_value.stringValue
else {
throw RxNeovimApi.Error
.exception(message: "Could not convert values to output.")
}
return output
})
.subscribe(on: self.scheduler)
}
@ -209,22 +230,52 @@ public extension NvimView {
.subscribe(on: self.scheduler)
}
func didBecomeMain() -> Completable { self.bridge.focusGained(true) }
func didBecomeMain() -> Completable {
self.focusGained(true)
}
func didResignMain() -> Completable { self.bridge.focusGained(false) }
func didResignMain() -> Completable {
self.focusGained(false)
}
internal func neoVimBuffer(
for buf: RxNeovimApi.Buffer,
currentBuffer: RxNeovimApi.Buffer?
) -> Single<NvimView.Buffer> {
self.api
.bufGetInfo(buffer: buf)
.map { info -> NvimView.Buffer in
.execLua(code: """
local function map(tbl, f)
local t = {}
for k,v in pairs(tbl) do
t[k] = f(v)
end
return t
end
return map(vim.fn.getbufinfo(...), function(i)
i.buftype = vim.api.nvim_get_option_value("buftype",
{buf=i.bufnr})
return i
end)
""", args: [MessagePackValue(buf.handle)])
.map { result -> NvimView.Buffer in
guard let info_array = result.arrayValue,
info_array.count == 1,
let raw_info = info_array[0].dictionaryValue
else {
throw RxNeovimApi.Error
.exception(message: "Could not convert values into info array.")
}
let info : [String: MessagePackValue] = Dictionary<String, MessagePackValue>(
uniqueKeysWithValues: raw_info.map({
(key: MessagePackValue, value: MessagePackValue) in
(key.stringValue!, value)
}))
let current = buf == currentBuffer
guard let path = info["filename"]?.stringValue,
let dirty = info["modified"]?.boolValue,
guard let path = info["name"]?.stringValue,
let dirty = info["changed"]?.intValue,
let buftype = info["buftype"]?.stringValue,
let listed = info["buflisted"]?.boolValue
let listed = info["listed"]?.intValue
else {
throw RxNeovimApi.Error
.exception(message: "Could not convert values from the dictionary.")
@ -236,9 +287,9 @@ public extension NvimView {
apiBuffer: buf,
url: url,
type: buftype,
isDirty: dirty,
isDirty: dirty != 0,
isCurrent: current,
isListed: listed
isListed: listed != 0
)
}
.subscribe(on: self.scheduler)

View File

@ -97,11 +97,10 @@ extension NvimView {
return
}
guard self.modeInfoList.count > self.mode.rawValue else {
guard let modeInfo = modeInfos[self.mode.rawValue] else {
self.log.error("Could not get modeInfo for mode index \(self.mode.rawValue)")
return
}
let modeInfo = modeInfoList[Int(mode.rawValue)]
guard let cursorAttrId = modeInfo.attrId,
let cursorShapeAttrs = self.cellAttributesCollection.attributes(

View File

@ -58,9 +58,9 @@ public extension NvimView {
default: return
}
try? self.bridge
.deleteCharacters(0, andInputEscapedString: self.vimPlainString(text))
.wait()
//try? self.api.feedkeys(keys: self.vimPlainString(text), mode:"m", escape_ks: false)
// .wait()
_ = self.api.input(keys: self.vimPlainString(text), errWhenBlocked: false).syncValue()
if self.hasMarkedText() { self._unmarkText() }
self.keyDownDone = true
@ -182,9 +182,9 @@ public extension NvimView {
// after delete, cusor should be the location
}
if replacementRange.length > 0 {
try? self.bridge
.deleteCharacters(replacementRange.length, andInputEscapedString: "")
.wait()
let text = String(repeating:"<BS>", count: replacementRange.length)
try? self.api.feedkeys(keys: text, mode:"i", escape_ks: false)
.wait()
}
// delay to wait async gui update handled.

View File

@ -5,6 +5,7 @@
import Cocoa
import RxSwift
import RxNeovim
public extension NvimView {
override func mouseDown(with event: NSEvent) {
@ -86,9 +87,26 @@ public extension NvimView {
min(Int(abs(deltaCellX)), maxScrollDeltaX),
min(Int(abs(deltaCellY)), maxScrollDeltaY)
)
let (horizSign, vertSign) = (deltaCellX > 0 ? 1 : -1, deltaCellY > 0 ? 1 : -1)
self.bridge
.scroll(horizontal: horizSign * absDeltaX, vertical: vertSign * absDeltaY, at: cellPosition)
var (horizSign, vertSign) = (deltaCellX > 0 ? 1 : -1, deltaCellY > 0 ? 1 : -1)
if event.isDirectionInvertedFromDevice {
vertSign = -vertSign
}
self.log.debug("# scroll: \(cellPosition.row + vertSign * absDeltaY) \(cellPosition.column + horizSign * absDeltaX)")
self.api.winGetCursor(window: RxNeovimApi.Window(0))
.map( {
guard $0.count == 2
else {
self.log.error("Error decoding \($0)")
return
}
self.api.winSetCursor(window: RxNeovimApi.Window(0),
pos: [$0[0] + vertSign * absDeltaY, $0[1] + horizSign * absDeltaX])
.subscribe(onError: { [weak self] error in
self?.log.error("Error in \(#function): \(error)")
})
.disposed(by: self.disposeBag)
})
.subscribe(onError: { [weak self] error in
self?.log.error("Error in \(#function): \(error)")
})

View File

@ -102,6 +102,8 @@ extension NvimView {
gui.async {
self.font = newFont
// Cell size likely changed, do a resize.
self.resizeNeoVimUi(to: self.frame.size)
self.markForRenderWholeView()
self.eventsSubject.onNext(.guifontChanged(newFont))
}

View File

@ -5,6 +5,8 @@
import Cocoa
import RxSwift
import RxNeovim
import MessagePack
extension NvimView {
override public func setFrameSize(_ newSize: NSSize) {
@ -13,6 +15,9 @@ extension NvimView {
if self.isInitialResize {
self.isInitialResize = false
self.launchNeoVim(self.discreteSize(size: newSize))
// FIXME: not clear why this is needed but otherwise
// grid is too large
self.resizeNeoVimUi(to: newSize)
return
}
@ -53,8 +58,7 @@ extension NvimView {
self.offset.x = floor((size.width - self.cellSize.width * discreteSize.width.cgf) / 2)
self.offset.y = floor((size.height - self.cellSize.height * discreteSize.height.cgf) / 2)
self.bridge
.resize(width: discreteSize.width, height: discreteSize.height)
self.api.uiTryResize(width: discreteSize.width, height: discreteSize.height)
.subscribe(onError: { [weak self] error in
self?.log.error("Error in \(#function): \(error)")
})
@ -63,28 +67,84 @@ extension NvimView {
private func launchNeoVim(_ size: Size) {
self.log.info("=== Starting neovim...")
let sockPath = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent("vimr_\(self.uuid).sock").path
let sockPath = self.bridge.listenAddress
self.log.info("NVIM_LISTEN_ADDRESS=\(sockPath)")
self.bridge.runLocalServerAndNvim(width: size.width, height: size.height)
// FIXME: need to wait for listen to occur
Thread.sleep(forTimeInterval: 0.1)
// We wait here, since the user of NvimView cannot subscribe
// on the Completable. We could demand that the user call launchNeoVim()
// by themselves, but...
try? self.bridge
.runLocalServerAndNvim(width: size.width, height: size.height)
.andThen(self.api.run(at: sockPath))
try?
self.api.run(at: sockPath)
.andThen(
self.sourceFileUrls.reduce(Completable.empty()) { prev, url in
prev
.andThen(
self.api.exec(src: "source \(url.shellEscapedPath)", output: true)
.asCompletable()
)
}
)
.andThen(self.api.subscribe(event: NvimView.rpcEventName))
.wait()
self.api.getApiInfo().map({
value in
guard let info = value.arrayValue,
info.count == 2,
let channel = info[0].int32Value
else {
throw RxNeovimApi.Error
.exception(message: "Could not convert values to api info.")
}
return channel
}).flatMapCompletable({
// FIXME: make lua
self.api.exec2(src: """
":augroup vimr
":augroup!
:autocmd VimEnter * call rpcnotify(\($0), 'autocommand', 'vimenter')
:autocmd BufWinEnter * call rpcnotify(\($0), 'autocommand', 'bufwinenter', str2nr(expand('<abuf>')))
:autocmd BufWinEnter * call rpcnotify(\($0), 'autocommand', 'bufwinleave', str2nr(expand('<abuf>')))
:autocmd TabEnter * call rpcnotify(\($0), 'autocommand', 'tabenter', str2nr(expand('<abuf>')))
:autocmd BufWritePost * call rpcnotify(\($0), 'autocommand', 'bufwritepost', str2nr(expand('<abuf>')))
:autocmd BufEnter * call rpcnotify(\($0), 'autocommand', 'bufenter', str2nr(expand('<abuf>')))
:autocmd DirChanged * call rpcnotify(\($0), 'autocommand', 'dirchanged', expand('<afile>'))
:autocmd ColorScheme * call rpcnotify(\($0), 'autocommand', 'colorscheme', \
get(nvim_get_hl(0, {'id': hlID('Normal')}), 'fg', -1), \
get(nvim_get_hl(0, {'id': hlID('Normal')}), 'bg', -1), \
get(nvim_get_hl(0, {'id': hlID('Visual')}), 'fg', -1), \
get(nvim_get_hl(0, {'id': hlID('Visual')}), 'bg', -1), \
get(nvim_get_hl(0, {'id': hlID('Directory')}), 'fg', -1))
:autocmd ExitPre * call rpcnotify(\($0), 'autocommand', 'exitpre')
:autocmd BufModifiedSet * call rpcnotify(\($0), 'autocommand', 'bufmodifiedset', \
str2nr(expand('<abuf>')), getbufinfo(str2nr(expand('<abuf>')))[0].changed)
:let g:gui_vimr = 1
":augroup END
""", opts: [:]).asCompletable()
.andThen(self.api.uiAttach(width: size.width, height: size.height, options: [
"ext_linegrid": true,
"ext_multigrid": false,
"ext_tabline": MessagePackValue(self.usesCustomTabBar),
"rgb": true
]))
.andThen(
self.sourceFileUrls.reduce(Completable.empty()) { prev, url in
prev
.andThen(
self.api.exec2(src: "source \(url.shellEscapedPath)", opts:["output": true])
.map({
retval in
guard let output_value = retval["output"] ?? retval["output"],
let output = output_value.stringValue
else {
throw RxNeovimApi.Error
.exception(message: "Could not convert values to output.")
}
return output
})
.asCompletable()
)
}
)
})
).wait()
}
private func randomEmoji() -> String {

View File

@ -24,6 +24,7 @@ public extension NvimView {
var usesCustomTabBar: Bool
var useInteractiveZsh: Bool
var cwd: URL
var nvimBinary: String
var nvimArgs: [String]?
var envDict: [String: String]?
var sourceFiles: [URL]
@ -32,6 +33,7 @@ public extension NvimView {
usesCustomTabBar: Bool,
useInteractiveZsh: Bool,
cwd: URL,
nvimBinary: String,
nvimArgs: [String]?,
envDict: [String: String]?,
sourceFiles: [URL]
@ -39,6 +41,7 @@ public extension NvimView {
self.usesCustomTabBar = usesCustomTabBar
self.useInteractiveZsh = useInteractiveZsh
self.cwd = cwd
self.nvimBinary = nvimBinary
self.nvimArgs = nvimArgs
self.envDict = envDict
self.sourceFiles = sourceFiles

View File

@ -28,43 +28,74 @@ extension NvimView {
}
final func resize(_ value: MessagePackValue) {
guard let array = MessagePackUtils.array(
from: value, ofSize: 2, conversion: { $0.intValue }
) else {
guard let array = value.arrayValue
else {
self.bridgeLogger.error("Could not convert \(value)")
return
}
guard array.count == 3 else {
self.bridgeLogger.error("Could not convert; wrong count: \(array)")
return
}
self.bridgeLogger.debug(array)
guard let grid = array[0].intValue,
let width = array[1].intValue,
let height = array[2].intValue
else{
self.bridgeLogger.error("Could not convert; wrong count: \(array)")
return
}
// FIXME: this must happen immediately, or subsequent updates fail
self.ugrid.resize(Size(width: width, height: height))
gui.async {
self.ugrid.resize(Size(width: array[0], height: array[1]))
self.markForRenderWholeView()
}
}
final func optionSet(_ values: [MessagePackValue]) {
var options : [MessagePackValue: MessagePackValue] = [:]
for index in 1..<values.count {
guard let option_pair = values[index].arrayValue,
option_pair.count == 2
else {
self.bridgeLogger.error("Could not convert \(values)")
continue
}
options[option_pair[0]] = option_pair[1]
}
self.handleRemoteOptions(options)
}
final func clear() {
self.bridgeLogger.debug()
self.ugrid.clear()
gui.async {
self.ugrid.clear()
self.markForRenderWholeView()
}
}
final func modeChange(_ value: MessagePackValue) {
guard let mode = MessagePackUtils.value(
from: value, conversion: { v -> CursorModeShape? in
guard let rawValue = v.intValue else { return nil }
return CursorModeShape(rawValue: UInt(rawValue))
}
) else {
guard let mainTuple = value.arrayValue,
mainTuple.count == 2,
let modeName = mainTuple[0].stringValue,
let modeIndex = mainTuple[1].uintValue
else {
self.bridgeLogger.error("Could not convert \(value)")
return
}
guard let modeShape = CursorModeShape(rawValue: modeName),
self.modeInfos[modeName] != nil
else {
self.bridgeLogger.error("Could not convert \(value)")
return
}
gui.async {
self.lastMode = self.mode
self.mode = mode
self.mode = modeShape
self.bridgeLogger.debug("\(self.lastMode) -> \(self.mode)")
self.handleInputMethodSource()
@ -78,67 +109,144 @@ extension NvimView {
self.bridgeLogger.trace("modeInfoSet: \(value)")
if let mainTuple = value.arrayValue,
mainTuple.count == 2,
let modeInfoList = mainTuple[1].arrayValue?.map(ModeInfo.init(withMsgPackDict:))
let modeInfoArray = mainTuple[1].arrayValue?.map({
let modeInfo = ModeInfo.init(withMsgPackDict:$0)
return (modeInfo.name, modeInfo)
})
{
self.modeInfoList = modeInfoList
self.modeInfos = Dictionary(
uniqueKeysWithValues: modeInfoArray)
}
}
final func flush(_ renderData: [MessagePackValue]) {
final func renderData(_ renderData: [MessagePackValue]) {
self.bridgeLogger.trace("# of render data: \(renderData.count)")
gui.async { [self] in
var (recompute, rowStart) = (false, Int.max)
renderData.forEach { value in
guard let renderEntry = value.arrayValue else { return }
guard renderEntry.count == 2 else { return }
guard renderEntry.count >= 2 else { return }
guard let rawType = renderEntry[0].intValue,
let innerArray = renderEntry[1].arrayValue,
let type = RenderDataType(rawValue: rawType)
guard let rawType = renderEntry[0].stringValue,
let innerArray = renderEntry[1].arrayValue
else {
self.bridgeLogger.error("Could not convert \(value)")
return
}
switch type {
case .rawLine:
let possibleNewRowStart = self.doRawLine(data: innerArray)
rowStart = min(rowStart, possibleNewRowStart)
switch rawType {
case "mode_change":
self.modeChange(renderEntry[1])
case "grid_line":
for index in 1..<renderEntry.count {
guard let grid_line = renderEntry[index].arrayValue
else {
self.bridgeLogger.error("Could not convert \(value)")
return
}
let possibleNewRowStart = self.doRawLineNu(data: grid_line)
rowStart = min(rowStart, possibleNewRowStart)
}
recompute = true
case .goto:
guard let row = innerArray[0].uint64Value,
let col = innerArray[1].uint64Value,
let textPositionRow = innerArray[2].uint64Value,
let textPositionCol = innerArray[3].uint64Value else { return }
case "grid_resize":
self.resize(renderEntry[1])
recompute = true
case "hl_attr_define":
for index in 1..<renderEntry.count {
self.setAttr(with: renderEntry[index])
}
case "default_colors_set":
self.defaultColors(with: renderEntry[1])
case "grid_clear":
self.clear()
recompute = true
case "win_viewport":
// FIXME: implement
self.winViewportUpdate(innerArray)
case "mouse_on":
self.mouseOn()
case "mouse_off":
self.mouseOff()
case "busy_start":
self.busyStart()
case "busy_stop":
self.busyStop()
case "option_set":
self.optionSet(renderEntry)
case "set_title":
self.setTitle(with: innerArray[0])
case "update_menu":
self.updateMenu()
case "bell":
self.bell()
case "visual_bell":
self.visualBell()
case "set_icon":
// FIXME
break
case "grid_cursor_goto":
guard let grid = innerArray[0].uintValue,
let row = innerArray[1].uintValue,
let col = innerArray[2].uintValue
else { return }
if let possibleNewRowStart = self.doGoto(
position: Position(row: Int(row), column: Int(col)),
textPosition: Position(row: Int(textPositionRow), column: Int(textPositionCol))
textPosition: Position(row: Int(row), column: Int(col))
) {
rowStart = min(rowStart, possibleNewRowStart)
recompute = true
}
case .scroll:
case "mode_info_set":
self.modeInfoSet(renderEntry[1])
case "grid_scroll":
let values = innerArray.compactMap(\.intValue)
guard values.count == 6 else {
guard values.count == 7 else {
self.bridgeLogger.error("Could not convert \(values)")
return
}
let possibleNewRowStart = self.doScroll(values)
let possibleNewRowStart = self.doScrollNu(values)
rowStart = min(possibleNewRowStart, rowStart)
recompute = true
@unknown default:
self.log.error("Unknown flush data type")
case "flush":
// FIXME: buffer up all the prior data
//self.markForRenderWholeView()
break
case "tabline_update":
self.tablineUpdate(innerArray)
default:
self.log.error("Unknown flush data type \(rawType)")
}
}
guard recompute else { return }
self.ugrid.recomputeFlatIndices(rowStart: rowStart)
if (rowStart < Int.max) {
self.ugrid.recomputeFlatIndices(rowStart: rowStart)
}
}
}
@ -163,7 +271,7 @@ extension NvimView {
completable(.completed)
return Disposables.create()
})
.andThen(self.bridge.quit())
.andThen(self.quit())
.subscribe(onCompleted: { [weak self] in
self?.bridgeLogger.info("Successfully stopped the bridge.")
self?.nvimExitedCondition.broadcast()
@ -173,55 +281,89 @@ extension NvimView {
.disposed(by: self.disposeBag)
}
final func autoCommandEvent(_ value: MessagePackValue) {
guard let array = MessagePackUtils.array(
from: value, ofSize: 2, conversion: { $0.intValue }
),
let event = NvimAutoCommandEvent(rawValue: array[0])
else {
self.bridgeLogger.error("Could not convert \(value)")
final func autoCommandEvent(_ array: [MessagePackValue]) {
guard array.count > 0,
let aucmd = array[0].stringValue?.lowercased(),
let event = NvimAutoCommandEvent(rawValue: aucmd)
else {
self.bridgeLogger.error("Could not convert \(array)")
return
}
self.bridgeLogger.debug("\(event): \(array)")
if event == .vimenter {
Completable
.empty()
.observe(on: SerialDispatchQueueScheduler(qos: .userInitiated))
.andThen(
Completable.create { completable in
self.rpcEventSubscriptionCondition.wait(for: 5)
self.bridgeLogger.debug("RPC events subscription done.")
completable(.completed)
return Disposables.create()
}
)
.andThen(
{
let ginitPath = URL(fileURLWithPath: NSHomeDirectory())
.appendingPathComponent(".config/nvim/ginit.vim").path
let loadGinit = FileManager.default.fileExists(atPath: ginitPath)
if loadGinit {
self.bridgeLogger.debug("Source'ing ginit.vim")
return self.api.command(command: "source \(ginitPath.shellEscapedPath)")
} else {
return .empty()
}
}()
)
//.andThen(self.bridge.notifyReadinessForRpcEvents())
.subscribe(onCompleted: { [weak self] in
self?.log.debug("Notified the NvimServer to fire GUIEnter")
})
.disposed(by: self.disposeBag)
return
}
if event == .exitpre {
self.stop()
return
}
self.bridgeLogger.debug("\(event): \(array)")
let bufferHandle = array[1]
if event == .vimenter {
Completable
.empty()
.observe(on: SerialDispatchQueueScheduler(qos: .userInitiated))
.andThen(
Completable.create { completable in
self.rpcEventSubscriptionCondition.wait(for: 5)
self.bridgeLogger.debug("RPC events subscription done.")
completable(.completed)
return Disposables.create()
}
)
.andThen(
{
let ginitPath = URL(fileURLWithPath: NSHomeDirectory())
.appendingPathComponent(".config/nvim/ginit.vim").path
let loadGinit = FileManager.default.fileExists(atPath: ginitPath)
if loadGinit {
self.bridgeLogger.debug("Source'ing ginit.vim")
return self.api.command(command: "source \(ginitPath.shellEscapedPath)")
} else {
return .empty()
}
}()
)
.andThen(self.bridge.notifyReadinessForRpcEvents())
.subscribe(onCompleted: { [weak self] in
self?.log.debug("Notified the NvimServer to fire GUIEnter")
})
.disposed(by: self.disposeBag)
if event == .dirchanged {
guard array.count > 1,
array[1].stringValue != nil
else {
self.bridgeLogger.error("Could not convert \(array)")
return
}
self.cwdChanged(array[1])
return
}
if event == .colorscheme {
self.colorSchemeChanged(MessagePackValue(Array(array[1..<array.count])))
return
}
guard array.count > 1,
let bufferHandle = array[1].intValue
else {
self.bridgeLogger.error("Could not convert \(array)")
return
}
if event == .bufmodifiedset {
guard array.count > 2
else {
self.bridgeLogger.error("Could not convert \(array)")
return
}
self.setDirty(with: array[2])
}
if event == .bufwinenter || event == .bufwinleave {
self.bufferListChanged()
}
@ -260,19 +402,37 @@ extension NvimView {
})
}
private func doRawLine(data: [MessagePackValue]) -> Int {
guard data.count == 7 else {
private func doRawLineNu(data: [MessagePackValue]) -> Int {
guard data.count == 5 else {
self.bridgeLogger.error("Could not convert; wrong count: \(data)")
return Int.max
}
guard let row = data[0].intValue,
let startCol = data[1].intValue,
let endCol = data[2].intValue, // past last index, but can be 0
let clearCol = data[3].intValue, // past last index (can be 0?)
let clearAttr = data[4].intValue,
let chunk = data[5].arrayValue?.compactMap(\.stringValue),
let attrIds = data[6].arrayValue?.compactMap(\.intValue)
guard let grid = data[0].intValue,
let row = data[1].intValue,
let startCol = data[2].intValue,
let chunk = data[3].arrayValue?.compactMap({
arg -> UUpdate? in
guard arg != nil,
let argArray = arg.arrayValue
else {
return nil
}
var string = ""
var attrId: Int? = nil
var repeats: Int? = nil
if (argArray.count > 0 && arg[0] != nil && arg[0]?.stringValue != nil) {
string = arg[0]!.stringValue!
}
if (argArray.count > 1 && arg[1] != nil && arg[1]?.intValue != nil) {
attrId = arg[1]!.intValue!
}
if (argArray.count > 2 && arg[2] != nil && arg[2]?.intValue != nil) {
repeats = arg[2]!.intValue!
}
return UUpdate(string: string, attrId: attrId, repeats: repeats)
}),
let wrap = data[4].boolValue
else {
self.bridgeLogger.error("Could not convert \(data)")
return Int.max
@ -281,24 +441,15 @@ extension NvimView {
#if TRACE
self.bridgeLogger.debug(
"row: \(row), startCol: \(startCol), endCol: \(endCol), " +
"clearCol: \(clearCol), clearAttr: \(clearAttr), " +
"chunk: \(chunk), attrIds: \(attrIds)"
"chunk: \(chunk)"
)
#endif
let count = endCol - startCol
guard chunk.count == count, attrIds.count == count else {
self.bridgeLogger.error("The count of chunks and attrIds do not match.")
return Int.max
}
self.ugrid.update(
let count = chunk.count
let endCol = self.ugrid.updateNu(
row: row,
startCol: startCol,
endCol: endCol,
clearCol: clearCol,
clearAttr: clearAttr,
chunk: chunk,
attrIds: attrIds
chunk: chunk
)
if count > 0 {
@ -324,12 +475,6 @@ extension NvimView {
}
}
if clearCol > endCol {
self.markForRender(region: Region(
top: row, bottom: row, left: endCol, right: max(endCol, clearCol - 1)
))
}
return row
}
@ -368,11 +513,11 @@ extension NvimView {
return rowStart
}
private func doScroll(_ array: [Int]) -> Int {
self.bridgeLogger.trace("[top, bot, left, right, rows, cols] = \(array)")
private func doScrollNu(_ array: [Int]) -> Int {
self.bridgeLogger.trace("[grid, top, bot, left, right, rows, cols] = \(array)")
let (top, bottom, left, right, rows, cols)
= (array[0], array[1] - 1, array[2], array[3] - 1, array[4], array[5])
let (grid, top, bottom, left, right, rows, cols)
= (array[0], array[1], array[2] - 1, array[3], array[4] - 1, array[5], array[6])
let scrollRegion = Region(
top: top, bottom: bottom,
@ -472,13 +617,13 @@ extension NvimView {
}
final func setDirty(with value: MessagePackValue) {
guard let dirty = value.boolValue else {
guard let dirty = value.intValue else {
self.bridgeLogger.error("Could not convert \(value)")
return
}
self.bridgeLogger.debug(dirty)
self.eventsSubject.onNext(.setDirtyStatus(dirty))
self.eventsSubject.onNext(.setDirtyStatus(dirty == 1))
}
final func rpcEventSubscribed() {
@ -486,6 +631,7 @@ extension NvimView {
self.eventsSubject.onNext(.rpcEventSubscribed)
}
// FIXME: convert to subprocess
final func bridgeHasFatalError(_ value: MessagePackValue?) {
gui.async {
let alert = NSAlert()
@ -522,60 +668,83 @@ extension NvimView {
}
}
final func event(_ value: MessagePackValue) {
guard let dict = value.dictionaryValue,
let event = dict.keys.first,
let args = dict[event]?.arrayValue
else {
self.bridgeLogger.error("Could not convert \(value)")
return
}
switch event.stringValue {
case "tabline_update": self.tablineUpdate(args)
case "win_viewport": self.winViewportUpdate(args)
default: break
}
}
final func setAttr(with value: MessagePackValue) {
guard let array = value.arrayValue else {
self.bridgeLogger.error("Could not convert \(value)")
return
}
guard array.count == 6 else {
guard array.count == 4 else {
self.bridgeLogger.error("Could not convert; wrong count \(value)")
return
}
guard let id = array[0].intValue,
let rawTrait = array[1].uint64Value,
let fg = array[2].intValue,
let bg = array[3].intValue,
let sp = array[4].intValue,
let reverse = array[5].boolValue
let rgb_dict = array[1].dictionaryValue,
let cterm_dict = array[2].dictionaryValue,
let info = array[3].arrayValue
else {
self.bridgeLogger.error(
"Could not get highlight attributes from " +
"\(value)"
"\(value)"
)
return
}
let trait = FontTrait(rawValue: UInt(rawTrait))
let mapped_rgb_dict = rgb_dict.map({
(key: MessagePackValue, value: MessagePackValue) in
(key.stringValue!, value)
})
let rgb_attr = Dictionary<String, MessagePackValue>(
uniqueKeysWithValues: mapped_rgb_dict)
let attrs = CellAttributes(
fontTrait: trait,
foreground: fg,
background: bg,
special: sp,
reverse: reverse
withDict: rgb_attr,
with: CellAttributes(fontTrait: FontTrait(), foreground: -1, background: -1, special:-1, reverse: false)
//self.cellAttributesCollection.defaultAttributes
)
self.bridgeLogger.debug("AttrId: \(id): \(attrs)")
gui.async {
// FIXME: seems to not work well unless not async
//gui.async {
self.cellAttributesCollection.set(attributes: attrs, for: id)
//}
}
final func defaultColors(with value: MessagePackValue) {
guard let array = value.arrayValue else {
self.bridgeLogger.error("Could not convert \(value)")
return
}
guard array.count == 5 else {
self.bridgeLogger.error("Could not convert; wrong count \(value)")
return
}
guard let rgb_fg = array[0].intValue,
let rgb_bg = array[1].intValue,
let rgb_sp = array[2].intValue,
let cterm_fg = array[3].intValue,
let cterm_bg = array[4].intValue
else {
self.bridgeLogger.error(
"Could not get default colors from " +
"\(value)"
)
return
}
let attrs = CellAttributes(
fontTrait: FontTrait(), foreground: rgb_fg, background: rgb_bg, special: rgb_sp, reverse: false)
//gui.async {
self.cellAttributesCollection.set(
attributes: attrs,
for: CellAttributesCollection.defaultAttributesId
)
self.layer?.backgroundColor = ColorUtils.cgColorIgnoringAlpha(
attrs.background
)
//}
}
final func updateMenu() {
@ -614,17 +783,17 @@ extension NvimView {
}
final func markForRender(region: Region) {
self.bridgeLogger.trace(region)
self.bridgeLogger.debug(region)
self.setNeedsDisplay(self.rect(for: region))
}
final func markForRender(row: Int, column: Int) {
self.bridgeLogger.trace("\(row):\(column)")
self.bridgeLogger.debug("\(row):\(column)")
self.setNeedsDisplay(self.rect(forRow: row, column: column))
}
final func markForRender(position: Position) {
self.bridgeLogger.trace(position)
self.bridgeLogger.debug(position)
self.setNeedsDisplay(
self.rect(forRow: position.row, column: position.column)
)
@ -649,7 +818,31 @@ extension NvimView {
gui.async { self.tabBar?.update(tabRepresentatives: self.tabEntries) }
}
func winViewportUpdate(_: [MessagePackValue]) {}
func winViewportUpdate(_ value: [MessagePackValue]) {
// FIXME
/*
guard let array = value.arrayValue,
array.count == 8
else {
self.bridgeLogger.error("Could not convert \(value)")
return
}
guard let grid = array[0].intValue,
let top = array[2].intValue,
let bot = array[3].intValue,
let curline = array[4].intValue,
let curcol = array[5].intValue,
let linecount = array[6].intValue,
let scroll_delta = array[6].intValue
else {
self.bridgeLogger.error("Could not convert \(value)")
return
}
// [top, bot, left, right, rows, cols]
// FIXMEL self.doScroll([])
*/
}
private func bufferWritten(_ handle: Int) {
self
@ -691,6 +884,15 @@ extension NvimView {
self.eventsSubject.onNext(.bufferListChanged)
self.updateTouchBarCurrentBuffer()
}
func focusGained(_ gained: Bool) -> Completable {
return self.api.uiSetFocus(gained: gained)
}
func quit() -> Completable {
return self.api.command(command: ":q")
.andThen(self.bridge.quit())
}
}
extension TISInputSource {

View File

@ -29,8 +29,6 @@ public protocol NvimViewDelegate: AnyObject {
public final class NvimView: NSView, NSUserInterfaceValidations, NSTextInputClient {
// MARK: - Public
public static let rpcEventName = "com.qvacua.NvimView"
public static let minFontSize = 4.0
public static let maxFontSize = 128.0
public static let defaultFont = NSFont.userFixedPitchFont(ofSize: 12)!
@ -53,8 +51,8 @@ public final class NvimView: NSView, NSUserInterfaceValidations, NSTextInputClie
public let uuid = UUID()
public let api = RxNeovimApi()
public internal(set) var mode = CursorModeShape.normal
public internal(set) var modeInfoList = [ModeInfo]()
public internal(set) var mode: CursorModeShape = .normal
public internal(set) var modeInfos = [String : ModeInfo]()
public internal(set) var theme = Theme.default
@ -172,9 +170,14 @@ public final class NvimView: NSView, NSUserInterfaceValidations, NSTextInputClie
switch msg {
case let .notification(method, params):
self?.log.debug("NOTIFICATION: \(method): \(params)")
guard method == NvimView.rpcEventName else { return }
self?.eventsSubject.onNext(.rpcEvent(params))
if (method == "redraw") {
self?.renderData(params)
} else if (method == "autocommand") {
self?.autoCommandEvent(params)
} else {
self?.log.debug("MSG ERROR: \(msg)")
}
case let .error(_, msg):
self?.log.debug("MSG ERROR: \(msg)")
@ -220,7 +223,6 @@ public final class NvimView: NSView, NSUserInterfaceValidations, NSTextInputClie
.disposed(by: db)
}
self.bridge.consumer = self
self.registerForDraggedTypes([NSPasteboard.PasteboardType.fileURL])
self.wantsLayer = true
@ -236,6 +238,7 @@ public final class NvimView: NSView, NSUserInterfaceValidations, NSTextInputClie
usesCustomTabBar: true,
useInteractiveZsh: false,
cwd: URL(fileURLWithPath: NSHomeDirectory()),
nvimBinary: "",
nvimArgs: nil,
envDict: nil,
sourceFiles: []

View File

@ -6,6 +6,18 @@
import Foundation
import os
struct UUpdate: Codable {
var string: String
var attrId: Int?
var repeats: Int?
init(string: String, attrId: Int? = nil, repeats: Int? = nil) {
self.string = string
self.attrId = attrId
self.repeats = repeats
}
}
struct UCell: Codable {
var string: String
var attrId: Int
@ -259,6 +271,40 @@ final class UGrid: CustomStringConvertible, Codable {
}
}
/// This does not recompute the flat char indices. For performance it's done
/// in NvimView.flush()
func updateNu(
row: Int,
startCol: Int,
chunk: [UUpdate]
) -> Int {
// remove marked patch and recover after modified from vim
var oldMarkedInfo: MarkedInfo?
if row == self.markedInfo?.position.row {
oldMarkedInfo = self.popMarkedInfo()
}
defer {
if let oldMarkedInfo = oldMarkedInfo {
updateMarkedInfo(newValue: oldMarkedInfo)
}
}
var lastAttrId : Int = 0
var column = startCol
for cindex in 0..<chunk.count {
let reps = chunk[cindex].repeats ?? 1
for _ in 0..<reps {
self.cells[row][column].string = chunk[cindex].string
let attrId = chunk[cindex].attrId
if attrId != nil {
lastAttrId = attrId!
}
self.cells[row][column].attrId = lastAttrId
column += 1
}
}
return column
}
struct MarkedInfo {
var position: Position
var markedCell: [UCell]

View File

@ -6,19 +6,16 @@
import Commons
import Foundation
import MessagePack
import NvimServerTypes
import os
import RxPack
import RxSwift
final class UiBridge {
weak var consumer: NvimView?
init(uuid: UUID, config: NvimView.Config) {
self.uuid = uuid
self.usesCustomTabBar = config.usesCustomTabBar
self.usesInteractiveZsh = config.useInteractiveZsh
self.nvimBinary = config.nvimBinary
self.nvimArgs = config.nvimArgs ?? []
self.cwd = config.cwd
@ -39,285 +36,83 @@ final class UiBridge {
queue: self.queue,
internalSerialQueueName: String(reflecting: UiBridge.self)
)
self.server.stream
.subscribe(onNext: { [weak self] message in
self?.handleMessage(msgId: message.msgid, data: message.data)
}, onError: { [weak self] error in
self?.log.error("There was an error on the local message port server: \(error)")
self?.consumer?.ipcBecameInvalid(error)
})
.disposed(by: self.disposeBag)
}
func runLocalServerAndNvim(width: Int, height: Int) -> Completable {
func runLocalServerAndNvim(width: Int, height: Int) {
self.initialWidth = width
self.initialHeight = height
return self.server
.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(.seconds(timeout), scheduler: self.scheduler)
}
func deleteCharacters(_ count: Int, andInputEscapedString string: String) -> Completable {
guard let strData = string.data(using: .utf8) else { return .empty() }
var data = Data(capacity: MemoryLayout<Int>.size + strData.count)
var c = count
withUnsafeBytes(of: &c) { data.append(contentsOf: $0) }
data.append(strData)
return self.sendMessage(msgId: .deleteInput, data: data)
}
func resize(width: Int, height: Int) -> Completable {
self.sendMessage(msgId: .resize, data: [width, height].data())
}
func notifyReadinessForRpcEvents() -> Completable {
self.sendMessage(msgId: .readyForRpcEvents, data: nil)
}
func focusGained(_ gained: Bool) -> Completable {
self.sendMessage(msgId: .focusGained, data: [gained].data())
}
func scroll(horizontal: Int, vertical: Int, at position: Position) -> Completable {
self.sendMessage(
msgId: .scroll,
data: [horizontal, vertical, position.row, position.column].data()
)
self.launchNvimUsingLoginShellEnv()
}
func quit() -> Completable {
self.quit {
Completable.create { completable in
self.nvimServerProc?.waitUntilExit()
self.log.info("NvimServer \(self.uuid) exited successfully.")
return Disposables.create()
}
}
func forceQuit() -> Completable {
self.log.fault("Force-exiting NvimServer \(self.uuid).")
return self.quit {
return Completable.create { completable in
self.forceExitNvimServer()
self.log.fault("NvimServer \(self.uuid) was forcefully exited.")
return Disposables.create()
}
}
func debug() -> Completable { self.sendMessage(msgId: .debug1, data: nil) }
private func handleMessage(msgId: Int32, data: Data?) {
guard let msg = NvimServerMsgId(rawValue: Int(msgId)) else { return }
switch msg {
case .serverReady:
self
.establishNvimConnection()
.subscribe(onError: { [weak self] error in self?.consumer?.ipcBecameInvalid(error) })
.disposed(by: self.disposeBag)
case .nvimReady:
self.runLocalServerAndNvimCompletable?(.completed)
self.runLocalServerAndNvimCompletable = nil
let isInitErrorPresent = MessagePackUtils
.value(from: data, conversion: { $0.boolValue }) ?? false
if isInitErrorPresent { self.consumer?.initVimError() }
case .resize:
guard let v = MessagePackUtils.value(from: data) else { return }
self.consumer?.resize(v)
case .clear:
self.consumer?.clear()
case .setMenu:
self.consumer?.updateMenu()
case .busyStart:
self.consumer?.busyStart()
case .busyStop:
self.consumer?.busyStop()
case .modeChange:
guard let v = MessagePackUtils.value(from: data) else { return }
self.consumer?.modeChange(v)
case .modeInfoSet:
guard let v = MessagePackUtils.value(from: data) else { return }
self.consumer?.modeInfoSet(v)
case .bell:
self.consumer?.bell()
case .visualBell:
self.consumer?.visualBell()
case .flush:
guard let d = data, let v = (try? unpackAll(d)) else { return }
self.consumer?.flush(v)
case .setTitle:
guard let v = MessagePackUtils.value(from: data) else { return }
self.consumer?.setTitle(with: v)
case .stop:
self.consumer?.stop()
case .dirtyStatusChanged:
guard let v = MessagePackUtils.value(from: data) else { return }
self.consumer?.setDirty(with: v)
case .cwdChanged:
guard let v = MessagePackUtils.value(from: data) else { return }
self.consumer?.cwdChanged(v)
case .defaultColorsChanged:
guard let v = MessagePackUtils.value(from: data) else { return }
self.consumer?.defaultColorsChanged(v)
case .colorSchemeChanged:
guard let v = MessagePackUtils.value(from: data) else { return }
self.consumer?.colorSchemeChanged(v)
case .optionSet:
guard let v = MessagePackUtils.value(from: data) else { return }
self.consumer?.optionSet(v)
case .autoCommandEvent:
guard let v = MessagePackUtils.value(from: data) else { return }
self.consumer?.autoCommandEvent(v)
case .event:
guard let v = MessagePackUtils.value(from: data) else { return }
self.consumer?.event(v)
case .debug1:
break
case .highlightAttrs:
guard let v = MessagePackUtils.value(from: data) else { return }
self.consumer?.setAttr(with: v)
case .rpcEventSubscribed:
self.consumer?.rpcEventSubscribed()
case .fatalError:
self.consumer?.bridgeHasFatalError(MessagePackUtils.value(from: data))
@unknown default:
self.log.error("Unkonwn msg type from NvimServer")
}
}
private func closePorts() -> Completable {
self.client
.stop()
.andThen(self.server.stop())
}
private func quit(using body: @escaping () -> Void) -> Completable {
self
.closePorts()
.andThen(Completable.create { completable in
body()
completable(.completed)
return Disposables.create()
})
}
private func establishNvimConnection() -> Completable {
self.client
.connect(to: self.remoteServerName)
.andThen(
self
.sendMessage(msgId: .agentReady, data: [self.initialWidth, self.initialHeight].data())
)
}
private func sendMessage(msgId: NvimBridgeMsgId, data: Data?) -> Completable {
self.client
.send(msgid: Int32(msgId.rawValue), data: data, expectsReply: false)
.asCompletable()
}
private func forceExitNvimServer() {
self.nvimServerProc?.interrupt()
self.nvimServerProc?.terminate()
}
private func launchNvimUsingLoginShellEnv() {
let listenAddress = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent("vimr_\(self.uuid).sock")
var env = self.envDict
env["VIMRUNTIME"] = Bundle.module.url(forResource: "runtime", withExtension: nil)!.path
env["NVIM_LISTEN_ADDRESS"] = listenAddress.path
env["NVIM_LISTEN_ADDRESS"] = self.listenAddress
self.log.debug("Socket: \(listenAddress.path)")
self.log.debug("Socket: \(self.listenAddress)")
let usesCustomTabBarArg = self.usesCustomTabBar ? "1" : "0"
let inPipe = Pipe()
let outPipe = Pipe()
let errorPipe = Pipe()
let process = Process()
process.environment = env
process.standardInput = inPipe
process.standardError = errorPipe
process.standardOutput = outPipe
process.currentDirectoryPath = self.cwd.path
// We know that NvimServer is there.
process.launchPath = Bundle.module.url(forResource: "NvimServer", withExtension: nil)!.path
if (self.nvimBinary != "" &&
FileManager.default.fileExists(atPath: self.nvimBinary)) {
process.launchPath = self.nvimBinary
} else {
// We know that NvimServer is there.
env["VIMRUNTIME"] = Bundle.module.url(forResource: "runtime", withExtension: nil)!.path
let launchPath = Bundle.module.url(forResource: "NvimServer", withExtension: nil)!.path
process.launchPath = launchPath
}
process.environment = env
process
.arguments = ["--gui", self.localServerName, self.remoteServerName, usesCustomTabBarArg] +
["--headless"] + self.nvimArgs
.arguments =
["--embed",
"--listen",
self.listenAddress] + self.nvimArgs
self.log.debug(
"Launching NvimServer with args: \(String(describing: process.arguments))"
"Launching NvimServer \(String(describing: process.launchPath)) with args: \(String(describing: process.arguments))"
)
process.launch()
do {
try process.run()
} catch {
return
}
self.nvimServerProc = process
}
// FIXME: GH-832
private func launchNvimUsingLoginShell() {
let nvimCmd = [
// We know that NvimServer is there.
Bundle.module.url(forResource: "NvimServer", withExtension: nil)!.path,
"--gui",
self.localServerName,
self.remoteServerName,
self.usesCustomTabBar ? "1" : "0",
"--headless",
] + self.nvimArgs
let listenAddress = FileManager.default.temporaryDirectory
.appendingPathComponent("vimr_\(self.uuid).sock")
let nvimEnv = [
// We know that runtime is there.
"VIMRUNTIME": Bundle.module.url(forResource: "runtime", withExtension: nil)!.path,
"NVIM_LISTEN_ADDRESS": listenAddress.path,
]
self.nvimServerProc = ProcessUtils.execProcessViaLoginShell(
cmd: nvimCmd.map { "'\($0)'" }.joined(separator: " "),
cwd: self.cwd,
envs: nvimEnv,
interactive: self.interactive(for: ProcessUtils.loginShell()),
qos: .userInteractive
)
}
private func interactive(for shell: URL) -> Bool {
if shell.lastPathComponent == "zsh" { return self.usesInteractiveZsh }
return true
@ -327,14 +122,11 @@ final class UiBridge {
private let uuid: UUID
private let usesCustomTabBar: Bool
private let usesInteractiveZsh: Bool
private let cwd: URL
private let nvimArgs: [String]
private let envDict: [String: String]
private let server = RxMessagePortServer(queueQos: .userInteractive)
private let client = RxMessagePortClient(queueQos: .userInteractive)
private let nvimBinary: String
private var nvimServerProc: Process?
@ -354,6 +146,11 @@ final class UiBridge {
private var localServerName: String { "com.qvacua.NvimView.\(self.uuid)" }
private var remoteServerName: String { "com.qvacua.NvimView.NvimServer.\(self.uuid)" }
var listenAddress: String {
return URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent("vimr_\(self.uuid).sock").path
}
}
private let timeout = 5

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
@ -339,6 +339,7 @@
packageReferences = (
4BD67C9524ECF4AB00147C51 /* XCRemoteSwiftPackageReference "MessagePack" */,
4BD67CCF24ED08CB00147C51 /* XCRemoteSwiftPackageReference "PureLayout" */,
1FF017012AF02F64003D62BB /* XCLocalSwiftPackageReference "../../NvimServer" */,
);
productRefGroup = 4B90F0051FD2AF59008A39E0 /* Products */;
projectDirPath = "";
@ -754,6 +755,13 @@
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
1FF017012AF02F64003D62BB /* XCLocalSwiftPackageReference "../../NvimServer" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../../NvimServer;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCRemoteSwiftPackageReference section */
4BD67C9524ECF4AB00147C51 /* XCRemoteSwiftPackageReference "MessagePack" */ = {
isa = XCRemoteSwiftPackageReference;

View File

@ -5,9 +5,18 @@
![Screenshot 1](https://raw.githubusercontent.com/qvacua/vimr/develop/resources/screenshot1.png)
![Screenshot 2](https://raw.githubusercontent.com/qvacua/vimr/develop/resources/screenshot2.png)
## Fork differences
Originially and wonderfully by [qvacua](https://github.com/qvacua/) which appears to be unsupported any more.
This version can talk to an arbitrary nvim (once the upstream bits get merged)
It therefore should be able to keep pace with nvim development better. Work was done to avoid
having to modify neovim, and the uibridge mechanism has been replaced with api calls.
## About
Project VimR is a Neovim GUI for macOS.
The goal is to build an editor that uses Neovim inside with many of the convenience
GUI features similar to those present in modern editors. We mainly use Swift,
but also use C/Objective-C when where appropriate.

View File

@ -1,4 +1,4 @@
// Auto generated for nvim version 0.8.2.
// Auto generated for nvim version 0.9.2.
// See bin/generate_api_methods.py
import Foundation
@ -983,36 +983,6 @@ extension RxNeovimApi {
.map(transform)
}
public func bufGetInfo(
buffer: RxNeovimApi.Buffer,
errWhenBlocked: Bool = true
) -> Single<Dictionary<String, RxNeovimApi.Value>> {
let params: [RxNeovimApi.Value] = [
.int(Int64(buffer.handle)),
]
func transform(_ value: Value) throws -> Dictionary<String, RxNeovimApi.Value> {
guard let result = (msgPackDictToSwift(value.dictionaryValue)) else {
throw RxNeovimApi.Error.conversion(type: Dictionary<String, RxNeovimApi.Value>.self)
}
return result
}
if errWhenBlocked {
return self
.checkBlocked(
self.rpc(method: "nvim_buf_get_info", params: params, expectsReturnValue: true)
)
.map(transform)
}
return self
.rpc(method: "nvim_buf_get_info", params: params, expectsReturnValue: true)
.map(transform)
}
public func parseCmd(
str: String,
opts: Dictionary<String, RxNeovimApi.Value>,
@ -1239,6 +1209,36 @@ extension RxNeovimApi {
.map(transform)
}
public func getOptionInfo(
name: String,
errWhenBlocked: Bool = true
) -> Single<Dictionary<String, RxNeovimApi.Value>> {
let params: [RxNeovimApi.Value] = [
.string(name),
]
func transform(_ value: Value) throws -> Dictionary<String, RxNeovimApi.Value> {
guard let result = (msgPackDictToSwift(value.dictionaryValue)) else {
throw RxNeovimApi.Error.conversion(type: Dictionary<String, RxNeovimApi.Value>.self)
}
return result
}
if errWhenBlocked {
return self
.checkBlocked(
self.rpc(method: "nvim_get_option_info", params: params, expectsReturnValue: true)
)
.map(transform)
}
return self
.rpc(method: "nvim_get_option_info", params: params, expectsReturnValue: true)
.map(transform)
}
public func createNamespace(
name: String,
errWhenBlocked: Bool = true
@ -1623,13 +1623,15 @@ extension RxNeovimApi {
.map(transform)
}
public func getOptionInfo(
public func getOptionInfo2(
name: String,
opts: Dictionary<String, RxNeovimApi.Value>,
errWhenBlocked: Bool = true
) -> Single<Dictionary<String, RxNeovimApi.Value>> {
let params: [RxNeovimApi.Value] = [
.string(name),
.map(opts.mapToDict({ (Value.string($0), $1) })),
]
func transform(_ value: Value) throws -> Dictionary<String, RxNeovimApi.Value> {
@ -1643,13 +1645,13 @@ extension RxNeovimApi {
if errWhenBlocked {
return self
.checkBlocked(
self.rpc(method: "nvim_get_option_info", params: params, expectsReturnValue: true)
self.rpc(method: "nvim_get_option_info2", params: params, expectsReturnValue: true)
)
.map(transform)
}
return self
.rpc(method: "nvim_get_option_info", params: params, expectsReturnValue: true)
.rpc(method: "nvim_get_option_info2", params: params, expectsReturnValue: true)
.map(transform)
}
@ -2051,6 +2053,28 @@ extension RxNeovimApi {
.asCompletable()
}
public func uiSetFocus(
gained: Bool,
expectsReturnValue: Bool = false
) -> Completable {
let params: [RxNeovimApi.Value] = [
.bool(gained),
]
if expectsReturnValue {
return self
.checkBlocked(
self.rpc(method: "nvim_ui_set_focus", params: params, expectsReturnValue: expectsReturnValue)
)
.asCompletable()
}
return self
.rpc(method: "nvim_ui_set_focus", params: params, expectsReturnValue: expectsReturnValue)
.asCompletable()
}
public func uiDetach(
expectsReturnValue: Bool = false
) -> Completable {
@ -2196,70 +2220,6 @@ extension RxNeovimApi {
.asCompletable()
}
public func getHlByName(
name: String,
rgb: Bool,
errWhenBlocked: Bool = true
) -> Single<Dictionary<String, RxNeovimApi.Value>> {
let params: [RxNeovimApi.Value] = [
.string(name),
.bool(rgb),
]
func transform(_ value: Value) throws -> Dictionary<String, RxNeovimApi.Value> {
guard let result = (msgPackDictToSwift(value.dictionaryValue)) else {
throw RxNeovimApi.Error.conversion(type: Dictionary<String, RxNeovimApi.Value>.self)
}
return result
}
if errWhenBlocked {
return self
.checkBlocked(
self.rpc(method: "nvim_get_hl_by_name", params: params, expectsReturnValue: true)
)
.map(transform)
}
return self
.rpc(method: "nvim_get_hl_by_name", params: params, expectsReturnValue: true)
.map(transform)
}
public func getHlById(
hl_id: Int,
rgb: Bool,
errWhenBlocked: Bool = true
) -> Single<Dictionary<String, RxNeovimApi.Value>> {
let params: [RxNeovimApi.Value] = [
.int(Int64(hl_id)),
.bool(rgb),
]
func transform(_ value: Value) throws -> Dictionary<String, RxNeovimApi.Value> {
guard let result = (msgPackDictToSwift(value.dictionaryValue)) else {
throw RxNeovimApi.Error.conversion(type: Dictionary<String, RxNeovimApi.Value>.self)
}
return result
}
if errWhenBlocked {
return self
.checkBlocked(
self.rpc(method: "nvim_get_hl_by_id", params: params, expectsReturnValue: true)
)
.map(transform)
}
return self
.rpc(method: "nvim_get_hl_by_id", params: params, expectsReturnValue: true)
.map(transform)
}
public func getHlIdByName(
name: String,
errWhenBlocked: Bool = true
@ -2290,6 +2250,38 @@ extension RxNeovimApi {
.map(transform)
}
public func getHl(
ns_id: Int,
opts: Dictionary<String, RxNeovimApi.Value>,
errWhenBlocked: Bool = true
) -> Single<Dictionary<String, RxNeovimApi.Value>> {
let params: [RxNeovimApi.Value] = [
.int(Int64(ns_id)),
.map(opts.mapToDict({ (Value.string($0), $1) })),
]
func transform(_ value: Value) throws -> Dictionary<String, RxNeovimApi.Value> {
guard let result = (msgPackDictToSwift(value.dictionaryValue)) else {
throw RxNeovimApi.Error.conversion(type: Dictionary<String, RxNeovimApi.Value>.self)
}
return result
}
if errWhenBlocked {
return self
.checkBlocked(
self.rpc(method: "nvim_get_hl", params: params, expectsReturnValue: true)
)
.map(transform)
}
return self
.rpc(method: "nvim_get_hl", params: params, expectsReturnValue: true)
.map(transform)
}
public func setHl(
ns_id: Int,
name: String,
@ -3968,49 +3960,20 @@ extension RxNeovimApi {
.map(transform)
}
public func getDirtyStatus(
errWhenBlocked: Bool = true
) -> Single<Bool> {
let params: [RxNeovimApi.Value] = [
]
func transform(_ value: Value) throws -> Bool {
guard let result = (value.boolValue) else {
throw RxNeovimApi.Error.conversion(type: Bool.self)
}
return result
}
if errWhenBlocked {
return self
.checkBlocked(
self.rpc(method: "nvim_get_dirty_status", params: params, expectsReturnValue: true)
)
.map(transform)
}
return self
.rpc(method: "nvim_get_dirty_status", params: params, expectsReturnValue: true)
.map(transform)
}
public func exec(
public func exec2(
src: String,
output: Bool,
opts: Dictionary<String, RxNeovimApi.Value>,
errWhenBlocked: Bool = true
) -> Single<String> {
) -> Single<Dictionary<String, RxNeovimApi.Value>> {
let params: [RxNeovimApi.Value] = [
.string(src),
.bool(output),
.map(opts.mapToDict({ (Value.string($0), $1) })),
]
func transform(_ value: Value) throws -> String {
guard let result = (value.stringValue) else {
throw RxNeovimApi.Error.conversion(type: String.self)
func transform(_ value: Value) throws -> Dictionary<String, RxNeovimApi.Value> {
guard let result = (msgPackDictToSwift(value.dictionaryValue)) else {
throw RxNeovimApi.Error.conversion(type: Dictionary<String, RxNeovimApi.Value>.self)
}
return result
@ -4019,13 +3982,13 @@ extension RxNeovimApi {
if errWhenBlocked {
return self
.checkBlocked(
self.rpc(method: "nvim_exec", params: params, expectsReturnValue: true)
self.rpc(method: "nvim_exec2", params: params, expectsReturnValue: true)
)
.map(transform)
}
return self
.rpc(method: "nvim_exec", params: params, expectsReturnValue: true)
.rpc(method: "nvim_exec2", params: params, expectsReturnValue: true)
.map(transform)
}

View File

@ -1,268 +0,0 @@
/// Tae Won Ha - http://taewon.de - @hataewon
/// See LICENSE
import Foundation
import RxSwift
public final class RxMessagePortClient {
public enum ResponseCode {
// Unfortunately, case success = kCFMessagePortSuccess is not possible.
case success
case sendTimeout
case receiveTimeout
case isInvalid
case transportError
case becameInvalidError
case unknown
fileprivate init(rawResponseCode code: Int32) {
switch code {
case kCFMessagePortSuccess: self = .success
case kCFMessagePortSendTimeout: self = .sendTimeout
case kCFMessagePortReceiveTimeout: self = .receiveTimeout
case kCFMessagePortIsInvalid: self = .isInvalid
case kCFMessagePortTransportError: self = .transportError
case kCFMessagePortBecameInvalidError: self = .becameInvalidError
default: self = .unknown
}
}
}
public enum Error: Swift.Error {
case serverInit
case clientInit
case portInvalid
case send(msgid: Int32, response: ResponseCode)
}
public static let defaultTimeout = CFTimeInterval(5)
public let uuid = UUID()
public var timeout = RxMessagePortClient.defaultTimeout
public init(queueQos: DispatchQoS) {
self.queue = DispatchQueue(
label: "\(String(reflecting: RxMessagePortClient.self))-\(self.uuid.uuidString)",
qos: queueQos,
target: .global(qos: queueQos.qosClass)
)
}
public func send(msgid: Int32, data: Data?, expectsReply: Bool) -> Single<Data?> {
Single.create { single in
self.queue.async {
guard CFMessagePortIsValid(self.port) else {
single(.failure(Error.portInvalid))
return
}
let returnDataPtr = UnsafeMutablePointer<Unmanaged<CFData>?>.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(.failure(
Error.send(msgid: msgid, response: ResponseCode(rawResponseCode: 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()
}
}
public func connect(to name: String) -> Completable {
Completable.create { completable in
self.queue.async {
self.port = CFMessagePortCreateRemote(kCFAllocatorDefault, name.cfstr)
if self.port == nil {
completable(.error(Error.clientInit))
return
}
completable(.completed)
}
return Disposables.create()
}
}
public func stop() -> Completable {
Completable.create { completable in
self.queue.async {
if self.port != nil, CFMessagePortIsValid(self.port) {
CFMessagePortInvalidate(self.port)
}
completable(.completed)
}
return Disposables.create()
}
}
private var port: CFMessagePort?
private let queue: DispatchQueue
}
public final class RxMessagePortServer {
public typealias SyncReplyBody = (Int32, Data?) -> Data?
public struct Message {
public var msgid: Int32
public var data: Data?
}
public let uuid = UUID()
public var syncReplyBody: SyncReplyBody? {
get { self.messageHandler.syncReplyBody }
set { self.messageHandler.syncReplyBody = newValue }
}
public var stream: Observable<Message> { self.streamSubject.asObservable() }
public init(queueQos: DispatchQoS) {
self.queue = DispatchQueue(
label: "\(String(reflecting: RxMessagePortClient.self))-\(self.uuid.uuidString)",
qos: queueQos,
target: .global(qos: queueQos.qosClass)
)
self.messageHandler = MessageHandler(subject: self.streamSubject)
self.portQueue = DispatchQueue(
label: "\(String(reflecting: RxMessagePortClient.self))-portQueue-\(self.uuid.uuidString)",
qos: queueQos
)
}
public func run(as name: String) -> Completable {
Completable.create { [unowned self] completable in
self.queue.async {
var localCtx = CFMessagePortContext(
version: 0,
info: Unmanaged.passUnretained(self.messageHandler).toOpaque(),
retain: nil,
release: nil,
copyDescription: nil
)
self.port = CFMessagePortCreateLocal(
kCFAllocatorDefault,
name.cfstr,
{ _, msgid, data, info in
guard let infoPtr = UnsafeRawPointer(info) else { return nil }
let handler = Unmanaged<MessageHandler>.fromOpaque(infoPtr).takeUnretainedValue()
return handler.handleMessage(msgId: msgid, cfdata: data)
},
&localCtx,
nil
)
if self.port == nil {
self.streamSubject.onError(RxMessagePortClient.Error.serverInit)
completable(.error(RxMessagePortClient.Error.serverInit))
}
self.portQueue.async { self.runServer() }
completable(.completed)
}
return Disposables.create()
}
}
public func stop() -> Completable {
Completable.create { completable in
self.queue.async {
self.messageHandler.syncReplyBody = nil
self.streamSubject.onCompleted()
if let portRunLoop = self.portRunLoop { CFRunLoopStop(portRunLoop) }
if self.port != nil, CFMessagePortIsValid(self.port) {
CFMessagePortInvalidate(self.port)
}
completable(.completed)
}
return Disposables.create()
}
}
private var port: CFMessagePort?
private let portQueue: DispatchQueue
private var portRunLoop: CFRunLoop?
private let queue: DispatchQueue
private var messageHandler: MessageHandler
private let streamSubject = PublishSubject<Message>()
private func runServer() {
self.portRunLoop = CFRunLoopGetCurrent()
let runLoopSrc = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, self.port, 0)
CFRunLoopAddSource(self.portRunLoop, runLoopSrc, .defaultMode)
CFRunLoopRun()
}
}
private class MessageHandler {
fileprivate var syncReplyBody: RxMessagePortServer.SyncReplyBody?
fileprivate init(subject: PublishSubject<RxMessagePortServer.Message>) { self.subject = subject }
fileprivate func handleMessage(msgId: Int32, cfdata: CFData?) -> Unmanaged<CFData>? {
let d = cfdata?.data
self.subject.onNext(RxMessagePortServer.Message(msgid: msgId, data: d))
guard let reply = self.syncReplyBody?(msgId, d) else { return nil }
// The system releases the returned CFData object.
// From https://developer.apple.com/documentation/corefoundation/cfmessageportcallback
// See also https://www.mikeash.com/pyblog/friday-qa-2017-08-11-swiftunmanaged.html
return Unmanaged.passRetained(reply.cfdata)
}
private let subject: PublishSubject<RxMessagePortServer.Message>
}
private extension Data {
var cfdata: CFData { self as NSData }
}
private extension CFData {
var data: Data { self as NSData as Data }
}
private extension String {
var cfstr: CFString { self as NSString }
}

View File

@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */
4B022661224AB1490052362B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B022660224AB1490052362B /* Assets.xcassets */; };
4B022664224AB1490052362B /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B022662224AB1490052362B /* MainMenu.xib */; };
4B2AD136293BC0C4009DA797 /* RxMessagePort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2AD135293BC0C4009DA797 /* RxMessagePort.swift */; };
4B2AD13B293BCC4E009DA797 /* RxPack in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2AD13A293BCC4E009DA797 /* RxPack */; };
4B7FBFD024EC8632002D12A1 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 4B7FBFCF24EC8632002D12A1 /* RxSwift */; };
4B7FBFD124EC8851002D12A1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B02265E224AB1490052362B /* AppDelegate.swift */; };
@ -34,7 +33,6 @@
4B022660224AB1490052362B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
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>"; };
4B2AD135293BC0C4009DA797 /* RxMessagePort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RxMessagePort.swift; path = ../Sources/RxPack/RxMessagePort.swift; sourceTree = "<group>"; };
4BE73FA8285DC4DA00B63585 /* RxPack */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = RxPack; path = ..; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -54,7 +52,6 @@
4B02263A224AB11A0052362B = {
isa = PBXGroup;
children = (
4B2AD135293BC0C4009DA797 /* RxMessagePort.swift */,
4BE73FA7285DC4DA00B63585 /* Packages */,
4B02265D224AB1490052362B /* RxMessagePortDemo */,
4B022644224AB11A0052362B /* Products */,
@ -127,8 +124,9 @@
4B02263B224AB11A0052362B /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1020;
LastUpgradeCheck = 1340;
LastUpgradeCheck = 1500;
ORGANIZATIONNAME = "Tae Won Ha";
TargetAttributes = {
4B02265B224AB1490052362B = {
@ -174,7 +172,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4B2AD136293BC0C4009DA797 /* RxMessagePort.swift in Sources */,
4B7FBFD124EC8851002D12A1 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -228,9 +225,11 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@ -245,7 +244,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@ -291,9 +290,11 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = fast;
@ -303,7 +304,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SWIFT_COMPILATION_MODE = wholemodule;
@ -318,11 +319,13 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
INFOPLIST_FILE = RxMessagePortDemo/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.qvacua.RxMessagePortDemo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
@ -334,11 +337,13 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
INFOPLIST_FILE = RxMessagePortDemo/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.qvacua.RxMessagePortDemo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;

View File

@ -16,9 +16,6 @@
<FileRef
location = "group:Tabs">
</FileRef>
<FileRef
location = "group:NvimServer">
</FileRef>
<FileRef
location = "group:NvimView">
</FileRef>

View File

@ -14,8 +14,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/mattgallagher/CwlCatchException.git",
"state" : {
"revision" : "35f9e770f54ce62dd8526470f14c6e137cef3eea",
"version" : "2.1.1"
"revision" : "3b123999de19bf04905bc1dfdb76f817b0f2cc00",
"version" : "2.1.2"
}
},
{
@ -23,8 +23,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git",
"state" : {
"revision" : "c21f7bab5ca8eee0a9998bbd17ca1d0eb45d4688",
"version" : "2.1.0"
"revision" : "a23ded2c91df9156628a6996ab4f347526f17b6b",
"version" : "2.1.2"
}
},
{

View File

@ -1224,6 +1224,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
OTHER_LDFLAGS = "-pthread";
PRODUCT_BUNDLE_IDENTIFIER = "$(VIMR_BUNDLE_IDENTIFIER)";
PRODUCT_MODULE_NAME = VimR;
@ -1246,6 +1247,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
OTHER_LDFLAGS = "-pthread";
PRODUCT_BUNDLE_IDENTIFIER = "$(VIMR_BUNDLE_IDENTIFIER)";
PRODUCT_MODULE_NAME = VimR;

View File

@ -26,6 +26,9 @@ final class AdvancedPrefReducer: ReducerType {
case let .setUseSnapshotUpdate(value):
state.useSnapshotUpdate = value
case let .setNvimBinary(value):
state.mainWindowTemplate.nvimBinary = value
}
return (state, pair.action, true)

View File

@ -15,6 +15,7 @@ final class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate {
case setUseSnapshotUpdate(Bool)
case setUseLiveResize(Bool)
case setDrawsParallel(Bool)
case setNvimBinary(String)
}
override var displayName: String {
@ -32,6 +33,7 @@ final class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate {
self.useSnapshotUpdate = state.useSnapshotUpdate
self.useLiveResize = state.mainWindowTemplate.useLiveResize
self.drawsParallel = state.mainWindowTemplate.drawsParallel
self.nvimBinary = state.mainWindowTemplate.nvimBinary
super.init(frame: .zero)
@ -42,11 +44,13 @@ final class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate {
.observe(on: MainScheduler.instance)
.subscribe(onNext: { state in
if self.useInteractiveZsh != state.mainWindowTemplate.useInteractiveZsh
|| self.nvimBinary != state.mainWindowTemplate.nvimBinary
|| self.useSnapshotUpdate != state.useSnapshotUpdate
|| self.useLiveResize != state.mainWindowTemplate.useLiveResize
|| self.drawsParallel != state.mainWindowTemplate.drawsParallel
{
self.useInteractiveZsh = state.mainWindowTemplate.useInteractiveZsh
self.nvimBinary = state.mainWindowTemplate.nvimBinary
self.useSnapshotUpdate = state.useSnapshotUpdate
self.useLiveResize = state.mainWindowTemplate.useLiveResize
self.drawsParallel = state.mainWindowTemplate.drawsParallel
@ -64,22 +68,29 @@ final class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate {
private var useSnapshotUpdate: Bool
private var useLiveResize: Bool
private var drawsParallel: Bool
private var nvimBinary: String = ""
private let useInteractiveZshCheckbox = NSButton(forAutoLayout: ())
private let useSnapshotUpdateCheckbox = NSButton(forAutoLayout: ())
private let useLiveResizeCheckbox = NSButton(forAutoLayout: ())
private let drawsParallelCheckbox = NSButton(forAutoLayout: ())
private let nvimBinaryField = NSTextView(forAutoLayout: ())
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func windowWillClose() {
self.nvimBinaryFieldAction()
}
private func updateViews() {
self.useSnapshotUpdateCheckbox.boolState = self.useSnapshotUpdate
self.useInteractiveZshCheckbox.boolState = self.useInteractiveZsh
self.useLiveResizeCheckbox.boolState = self.useLiveResize
self.drawsParallelCheckbox.boolState = self.drawsParallel
self.nvimBinaryField.string = self.nvimBinary ?? ""
}
private func addViews() {
@ -136,6 +147,9 @@ final class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate {
when scrolling very fast.
"""#)
let nvimBinaryTitle = self.titleTextField(title: "NeoVim Binary:")
let nvimBinaryField = self.nvimBinaryField
self.addSubview(paneTitle)
self.addSubview(useSnapshotUpdate)
@ -146,6 +160,8 @@ final class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate {
self.addSubview(useLiveResizeInfo)
self.addSubview(drawsParallelBox)
self.addSubview(drawsParallelInfo)
self.addSubview(nvimBinaryTitle)
self.addSubview(nvimBinaryField)
paneTitle.autoPinEdge(toSuperviewEdge: .top, withInset: 18)
paneTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18)
@ -174,6 +190,21 @@ final class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate {
useInteractiveZshInfo.autoPinEdge(.top, to: .bottom, of: useInteractiveZsh, withOffset: 5)
useInteractiveZshInfo.autoPinEdge(.left, to: .left, of: useInteractiveZsh)
nvimBinaryTitle.autoPinEdge(.top, to: .bottom, of: useInteractiveZshInfo, withOffset: 18)
nvimBinaryTitle.autoPinEdge(.left, to: .left, of: useLiveResize, withOffset: 5)
//nvimBinaryTitle.autoAlignAxis(.baseline, toSameAxisOf: nvimBinaryField)
nvimBinaryField.autoPinEdge(.top, to: .bottom, of: useInteractiveZshInfo, withOffset: 18)
nvimBinaryField.autoPinEdge(.left, to: .right, of: nvimBinaryTitle, withOffset: 5)
nvimBinaryField.autoPinEdge(toSuperviewEdge: .right, withInset: 18)
nvimBinaryField.autoSetDimension(.height, toSize: 20, relation: .greaterThanOrEqual)
NotificationCenter.default.addObserver(
forName: NSControl.textDidEndEditingNotification,
object: nvimBinaryField,
queue: nil
) { [weak self] _ in self?.nvimBinaryFieldAction() }
}
}
@ -195,4 +226,9 @@ extension AdvancedPref {
@objc func useSnapshotUpdateChannelAction(_ sender: NSButton) {
self.emit(.setUseSnapshotUpdate(sender.boolState))
}
func nvimBinaryFieldAction() {
let newNvimBinary = self.nvimBinaryField.string
self.emit(.setNvimBinary(newNvimBinary))
}
}

View File

@ -4,6 +4,7 @@
*/
import Cocoa
import MessagePack
import NvimView
import RxNeovim
import RxPack
@ -101,6 +102,8 @@ extension MainWindow {
for: .setTheme(Theme(from: nvimTheme, additionalColorDict: colors))
)
)
}, onError: {
err in self.log.trace("oops couldn't set theme")
})
.disposed(by: self.disposeBag)
}
@ -153,7 +156,8 @@ extension MainWindow {
(
colorName: colorName,
observable: self.neoVimView.api
.getHlByName(name: colorName, rgb: true)
.getHl(ns_id: 0,
opts: ["name": MessagePackValue(colorName)])
.asObservable()
)
})

View File

@ -86,6 +86,7 @@ final class MainWindow: NSObject,
usesCustomTabBar: state.appearance.usesCustomTab,
useInteractiveZsh: state.useInteractiveZsh,
cwd: state.cwd,
nvimBinary: state.nvimBinary,
nvimArgs: state.nvimArgs,
envDict: state.envDict,
sourceFiles: sourceFileUrls
@ -259,7 +260,7 @@ final class MainWindow: NSObject,
private var usesTheme = true
private var lastThemeMark = Token()
private let log = OSLog(
internal let log = OSLog(
subsystem: Defs.loggerSubsystem,
category: Defs.LoggerCategory.ui
)

View File

@ -20,6 +20,7 @@ final class PrefMiddleware: MiddlewareType {
do {
let dictionary: [String: Any] = try dictEncoder.encode(appState)
defaults.set(dictionary, forKey: PrefMiddleware.compatibleVersion)
defaults.synchronize()
} catch {
self.log.error("AppState could not converted to Dictionary: \(error)")
}
@ -59,6 +60,7 @@ final class PrefMiddleware: MiddlewareType {
do {
let dictionary: [String: Any] = try dictEncoder.encode(result.state)
defaults.set(dictionary, forKey: PrefMiddleware.compatibleVersion)
defaults.synchronize()
} catch {
self.log.error("AppState could not converted to Dictionary: \(error)")
}
@ -86,6 +88,7 @@ final class PrefMiddleware: MiddlewareType {
do {
let dictionary: [String: Any] = try dictEncoder.encode(result.state)
defaults.set(dictionary, forKey: PrefMiddleware.compatibleVersion)
defaults.synchronize()
} catch {
self.log.error("AppState could not converted to Dictionary: \(error)")
}

View File

@ -263,7 +263,9 @@ struct AppearanceState: Codable {
extension MainWindow {
struct State: Codable {
static let `default` = State(isAllToolsVisible: true, isToolButtonsVisible: true)
static let `default` = State(isAllToolsVisible: true,
isToolButtonsVisible: true,
nvimBinary: "")
static let defaultTools: [MainWindow.Tools: WorkspaceToolState] = [
.fileBrowser: WorkspaceToolState(location: .left, dimension: 200, open: true),
@ -317,6 +319,7 @@ extension MainWindow {
var appearance = AppearanceState.default
var useInteractiveZsh = false
var nvimBinary: String = ""
var nvimArgs: [String]?
var cliPipePath: String?
var envDict: [String: String]?
@ -326,15 +329,17 @@ extension MainWindow {
var isLeftOptionMeta = false
var isRightOptionMeta = false
// to be cleaned
var urlsToOpen = [URL: OpenMode]()
var currentBufferToSet: NvimView.Buffer?
var cwdToSet: URL?
var viewToBeFocused: FocusableView? = FocusableView.neoVimView
init(isAllToolsVisible: Bool, isToolButtonsVisible: Bool) {
init(isAllToolsVisible: Bool, isToolButtonsVisible: Bool, nvimBinary: String) {
self.isAllToolsVisible = isAllToolsVisible
self.isToolButtonsVisible = isToolButtonsVisible
self.nvimBinary = nvimBinary
}
enum CodingKeys: String, CodingKey {
@ -348,6 +353,8 @@ extension MainWindow {
case isRightOptionMeta = "is-right-option-meta"
case useInteractiveZsh = "use-interactive-zsh"
case nvimBinary = "nvim-binary"
case useLiveResize = "use-live-resize"
case drawsParallel = "draws-parallel"
case isShowHidden = "is-show-hidden"
@ -373,6 +380,7 @@ extension MainWindow {
forKey: .useInteractiveZsh,
default: State.default.useInteractiveZsh
)
self.nvimBinary = try container.decodeIfPresent(String.self, forKey: .nvimBinary) ?? State.default.nvimBinary
self.useLiveResize = try container.decode(
forKey: .useLiveResize,
default: State.default.useLiveResize
@ -459,6 +467,7 @@ extension MainWindow {
try container.encode(self.isLeftOptionMeta, forKey: .isLeftOptionMeta)
try container.encode(self.isRightOptionMeta, forKey: .isRightOptionMeta)
try container.encode(self.useInteractiveZsh, forKey: .useInteractiveZsh)
try container.encode(self.nvimBinary, forKey: .nvimBinary)
try container.encode(self.fileBrowserShowHidden, forKey: .isShowHidden)
// See [1]

View File

@ -13,7 +13,7 @@ prepare_nvimserver() {
# Build NvimServer and copy
build_libnvim=true ./NvimServer/NvimServer/bin/build_nvimserver.sh
cp ./NvimServer/.build/apple/Products/Release/NvimServer "${resources_folder}"
cp ./NvimServer/.build/arm64-apple-macosx/release/NvimServer "${resources_folder}"
# Create and copy runtime folder
install_path="$(/usr/bin/mktemp -d -t 'nvim-runtime')"
@ -38,10 +38,17 @@ build_vimr() {
echo "### Xcodebuilding"
rm -rf "${build_path}"
xcodebuild \
-configuration Release -derivedDataPath "${build_path}" \
-workspace VimR.xcworkspace -scheme VimR \
clean build
if [[ "${clean}" == true ]]; then
xcodebuild \
-configuration Release -derivedDataPath "${build_path}" \
-workspace VimR.xcworkspace -scheme VimR \
clean build
else
xcodebuild \
-configuration Release -derivedDataPath "${build_path}" \
-workspace VimR.xcworkspace -scheme VimR \
build
fi
}
main () {

View File

@ -25,7 +25,7 @@ def swift_autocmds(version: str, template_string: str) -> str:
return template.substitute(
event_cases="\n".join(
[f" case {event[0].lower()} = {event[1]}" for event in autocmds]
[f" case {event[0].lower()} = \"{event[0].lower()}\"" for event in autocmds]
),
version=version
)

View File

@ -46,7 +46,7 @@ def are_shapes_same() -> bool:
def swift_shapes() -> str:
with io.open(SWIFT_TEMPLATE_FILE, "r") as template_file:
template = Template(template_file.read())
cases = "\n".join([f" case {v[1]} = {v[0]}" for (k, v) in SHAPE_NAMES.items()])
cases = "\n".join([f" case {v[1]} = \"{v[1]}\"" for (k, v) in SHAPE_NAMES.items()])
return template.substitute(
cursor_shapes=cases,
version=version

View File

@ -2,7 +2,7 @@
set -Eeuo pipefail
readonly vimr_app_path=${vimr_app_path:?"Path to VimR.app"}
readonly identity="Developer ID Application: Tae Won Ha (H96Q2NKTQH)"
readonly identity="Developer ID Application: George Harker (B8V3694RNX)"
remove_sparkle_xpc () {
# VimR is not sandboxed, so, remove the XPCs

View File

@ -1,7 +1,7 @@
// Auto generated for nvim ${version}
// See bin/generate_autocmds.py
enum NvimAutoCommandEvent: Int {
enum NvimAutoCommandEvent: String {
${event_cases}
}

View File

@ -1,8 +1,7 @@
// Auto generated for nvim ${version}
// See bin/generate_cursor_shape.py
public enum CursorModeShape: UInt {
public enum CursorModeShape: String {
${cursor_shapes}
}