├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── build-universal-neovim.yml ├── .gitignore ├── .gitmodules ├── .swiftformat ├── .swiftlint.yml ├── Brewfile ├── Commons ├── .gitignore ├── Package.swift ├── README.md ├── Sources │ ├── Commons │ │ ├── AppKitCommons.swift │ │ ├── ConditionVariable.swift │ │ ├── CoreCommons.swift │ │ ├── Defs.swift │ │ ├── FifoCache.swift │ │ ├── FileUtils.swift │ │ ├── FoundationCommons.swift │ │ ├── OSLogCommons.swift │ │ ├── ProcessUtils.swift │ │ └── SwiftCommons.swift │ └── CommonsObjC │ │ ├── NetUtils.m │ │ └── include │ │ └── NetUtils.h ├── Support │ ├── CommonsSupport.xcodeproj │ │ └── project.pbxproj │ └── EnvVarTest │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Base.lproj │ │ └── MainMenu.xib │ │ └── Info.plist └── Tests │ └── CommonsTests │ ├── ArrayCommonsTest.swift │ ├── DictionaryCommonsTest.swift │ ├── FifoCacheTest.swift │ ├── FileUtilsTest.swift │ ├── Resources │ ├── FileUtilsTest │ │ ├── a1 │ │ │ ├── a1-file1 │ │ │ └── a2 │ │ │ │ └── a1-a2-file1 │ │ └── b1 │ │ │ └── b1-file1 │ └── UrlCommonsTest │ │ ├── .dot-hidden-file │ │ └── dummy.rtfd │ │ └── TXT.rtf │ ├── StringCommonsTest.swift │ ├── SwiftCommonsTest.swift │ └── UrlCommonsTest.swift ├── DEVELOP.md ├── Ignore ├── Package.swift ├── README.md ├── Sources │ └── Ignore │ │ ├── FileLineReader.swift │ │ ├── Filter.swift │ │ ├── GitUtils.swift │ │ └── Ignore.swift └── Tests │ └── IgnoreTests │ ├── FileLineReaderTest.swift │ ├── FilterTest.swift │ ├── IgnoreSplitTest.swift │ ├── IgnoreTest.swift │ └── Resources │ ├── FileLineReaderTest │ ├── dos-no-line-ending-at-the-end.txt │ ├── dos-only-new-lines.txt │ ├── dos-with-line-ending-at-the-end.txt │ ├── empty.txt │ ├── unix-no-line-ending-at-the-end.txt │ ├── unix-only-new-lines.txt │ └── unix-with-line-ending-at-the-end.txt │ └── IgnoreCollectionTest │ ├── .gitignore │ ├── .ignore │ ├── ignore-splitting-0 │ ├── ignore-splitting-1 │ ├── ignore-splitting-2 │ ├── ignore-splitting-3 │ └── ignore-splitting-4 ├── LICENSE ├── NvimView ├── .gitignore ├── Package.swift ├── README.md ├── Sources │ └── NvimView │ │ ├── AttributesRunDrawer.swift │ │ ├── CellAttributes.swift │ │ ├── CellAttributesCollection.swift │ │ ├── ColorUtils.swift │ │ ├── Defs.swift │ │ ├── FontUtils.swift │ │ ├── Geometry.swift │ │ ├── KeyUtils.swift │ │ ├── MessagePackCommons.swift │ │ ├── ModeInfo.swift │ │ ├── NvimAutoCommandEvent.generated.swift │ │ ├── NvimCursorModeShape.generated.swift │ │ ├── NvimView+Api.swift │ │ ├── NvimView+Debug.swift │ │ ├── NvimView+Dragging.swift │ │ ├── NvimView+Draw.swift │ │ ├── NvimView+Geometry.swift │ │ ├── NvimView+Key.swift │ │ ├── NvimView+MenuItems.swift │ │ ├── NvimView+Mouse.swift │ │ ├── NvimView+Objects.swift │ │ ├── NvimView+RemoteOptions.swift │ │ ├── NvimView+Resize.swift │ │ ├── NvimView+TouchBar.swift │ │ ├── NvimView+Types.swift │ │ ├── NvimView+UiBridge.swift │ │ ├── NvimView.swift │ │ ├── Resources │ │ └── com.qvacua.NvimView.vim │ │ ├── Runs.swift │ │ ├── Typesetter.swift │ │ ├── UGrid.swift │ │ └── UiBridge.swift ├── Support │ ├── DrawerDev │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ ├── Info.plist │ │ ├── MyView.swift │ │ └── NvimView.swift │ ├── DrawerPerf │ │ ├── 0.json │ │ ├── 1.json │ │ ├── 2.json │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ ├── FontTrait.swift │ │ ├── Info.plist │ │ ├── NvimView.swift │ │ └── PerfTester.swift │ ├── MinimalNvimViewDemo │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ ├── Document.xib │ │ │ └── MainMenu.xib │ │ ├── Document.swift │ │ └── Info.plist │ └── NvimViewSupport.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ │ └── xcshareddata │ │ └── xcschemes │ │ └── MinimalNvimViewDemo.xcscheme └── Tests │ └── NvimViewTests │ ├── CellAttributesCollectionTest.swift │ ├── NimbleCommons.swift │ ├── TypesetterTest.swift │ └── UGridTest.swift ├── README.md ├── RxPack ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources │ ├── RxNeovim │ │ ├── RxNeovimApi.generated.swift │ │ └── RxNeovimApi.swift │ └── RxPack │ │ ├── RxMsgpackRpc.swift │ │ └── RxSwiftCommons.swift ├── Tests │ ├── RxNeovimTests │ │ └── RxNeovimApiExample.swift │ └── RxPackTests │ │ └── RxMsgpackRpcNeovimExample.swift └── bin │ ├── generate_api_methods.py │ ├── generate_sources.sh │ └── requirements.txt ├── Tabs ├── .gitignore ├── .swiftpm │ └── xcode │ │ └── package.xcworkspace │ │ └── contents.xcworkspacedata ├── Package.swift ├── README.md ├── Sources │ └── Tabs │ │ ├── DraggingSingleRowStackView.swift │ │ ├── HorizontalOnlyScrollView.swift │ │ ├── Tab.swift │ │ ├── TabBar.swift │ │ └── Theme.swift ├── Support │ ├── TabsSupport.xcodeproj │ │ └── project.pbxproj │ └── TabsSupport │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Base.lproj │ │ └── MainMenu.xib │ │ └── Info.plist └── Tests │ └── TabsTests │ └── TabsTests.swift ├── VimR.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── VimR ├── Dev.xcconfig ├── Release.xcconfig ├── VimR.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ └── VimR.xcscheme ├── VimR │ ├── AdvancedPrefReducer.swift │ ├── AdvencedPref.swift │ ├── AppDelegate.swift │ ├── AppDelegateReducer.swift │ ├── AppKitCommons.swift │ ├── AppearancePref.swift │ ├── AppearancePrefReducer.swift │ ├── Application.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon_128x128.png │ │ │ ├── icon_128x128@2x.png │ │ │ ├── icon_16x16.png │ │ │ ├── icon_16x16@2x.png │ │ │ ├── icon_256x256.png │ │ │ ├── icon_256x256@2x.png │ │ │ ├── icon_32x32.png │ │ │ └── icon_512x512.png │ ├── Base.lproj │ │ ├── Credits.rtf │ │ ├── FileBrowserMenu.xib │ │ ├── MainMenu.xib │ │ ├── MainWindow.xib │ │ ├── OpenQuicklyWindow.xib │ │ └── PrefWindow.xib │ ├── Bridge.h │ ├── BufferList.swift │ ├── BufferListReducer.swift │ ├── Context.swift │ ├── CoreDataStack.swift │ ├── CssUtils.swift │ ├── Debouncer.swift │ ├── DefaultShortcuts.swift │ ├── Defs.swift │ ├── FileBrowser.swift │ ├── FileBrowserReducer.swift │ ├── FileItem+CoreDataClass.h │ ├── FileItem+CoreDataClass.m │ ├── FileMonitor.swift │ ├── FileOutlineView.swift │ ├── FoundationCommons.swift │ ├── FuzzySearch.xcdatamodel │ │ └── contents │ ├── FuzzySearchService.swift │ ├── GeneralPref.swift │ ├── GeneralPrefReducer.swift │ ├── HtmlPreviewMiddleware.swift │ ├── HtmlPreviewTool.swift │ ├── HtmlPreviewToolReducer.swift │ ├── HttpServerMiddleware.swift │ ├── IgnoreService.swift │ ├── ImageAndTextTableCell.swift │ ├── Info.plist │ ├── KeysPref.swift │ ├── KeysPrefReducer.swift │ ├── MainWindow+Actions.swift │ ├── MainWindow+CustomTitle.swift │ ├── MainWindow+Delegates.swift │ ├── MainWindow+Types.swift │ ├── MainWindow.swift │ ├── MainWindowReducer.swift │ ├── MarkdownPreviewMiddleware.swift │ ├── MarkdownPreviewReducer.swift │ ├── MarkdownTool.swift │ ├── MarkdownToolReducer.swift │ ├── OpenQuicklyFileViewRow.swift │ ├── OpenQuicklyReducer.swift │ ├── OpenQuicklyWindow.swift │ ├── PrefMiddleware.swift │ ├── PrefPane.swift │ ├── PrefUtils.swift │ ├── PrefWindow.swift │ ├── PrefWindowReducer.swift │ ├── Resources.swift │ ├── RpcAppearanceEpic.swift │ ├── RpcEvents.swift │ ├── RxCommons.swift │ ├── RxRedux.swift │ ├── ScoredUrl.h │ ├── ScoredUrl.m │ ├── ShortcutItem.swift │ ├── ShortcutService.swift │ ├── ShortcutsPref.swift │ ├── ShortcutsTableSubviews.swift │ ├── States.swift │ ├── Theme.swift │ ├── ThemedTableSubviews.swift │ ├── ToolsPref.swift │ ├── ToolsPrefReducer.swift │ ├── Types.swift │ ├── UiRoot.swift │ ├── UiRootReducer.swift │ ├── com.qvacua.VimR.vim │ ├── macvim-file-icons │ │ ├── MacVim-applescript.icns │ │ ├── MacVim-as.icns │ │ ├── MacVim-asp.icns │ │ ├── MacVim-bash.icns │ │ ├── MacVim-bib.icns │ │ ├── MacVim-bsh.icns │ │ ├── MacVim-c.icns │ │ ├── MacVim-cfg.icns │ │ ├── MacVim-cgi.icns │ │ ├── MacVim-cpp.icns │ │ ├── MacVim-cs.icns │ │ ├── MacVim-csfg.icns │ │ ├── MacVim-css.icns │ │ ├── MacVim-csv.icns │ │ ├── MacVim-dtd.icns │ │ ├── MacVim-dylan.icns │ │ ├── MacVim-erl.icns │ │ ├── MacVim-f.icns │ │ ├── MacVim-fscript.icns │ │ ├── MacVim-generic.icns │ │ ├── MacVim-gtd.icns │ │ ├── MacVim-h.icns │ │ ├── MacVim-hs.icns │ │ ├── MacVim-html.icns │ │ ├── MacVim-ics.icns │ │ ├── MacVim-inc.icns │ │ ├── MacVim-ini.icns │ │ ├── MacVim-io.icns │ │ ├── MacVim-java.icns │ │ ├── MacVim-js.icns │ │ ├── MacVim-jsp.icns │ │ ├── MacVim-lisp.icns │ │ ├── MacVim-log.icns │ │ ├── MacVim-m.icns │ │ ├── MacVim-markdown.icns │ │ ├── MacVim-mm.icns │ │ ├── MacVim-patch.icns │ │ ├── MacVim-perl.icns │ │ ├── MacVim-php.icns │ │ ├── MacVim-plist.icns │ │ ├── MacVim-properties.icns │ │ ├── MacVim-ps.icns │ │ ├── MacVim-py.icns │ │ ├── MacVim-rb.icns │ │ ├── MacVim-rst.icns │ │ ├── MacVim-sch.icns │ │ ├── MacVim-sql.icns │ │ ├── MacVim-tcl.icns │ │ ├── MacVim-tex.icns │ │ ├── MacVim-tsv.icns │ │ ├── MacVim-txt.icns │ │ ├── MacVim-vb.icns │ │ ├── MacVim-vba.icns │ │ ├── MacVim-vcf.icns │ │ ├── MacVim-vim.icns │ │ ├── MacVim-wiki.icns │ │ ├── MacVim-xml.icns │ │ ├── MacVim-xsl.icns │ │ └── MacVim-yaml.icns │ ├── markdown │ │ ├── color-overrides.css │ │ ├── github-markdown.css │ │ └── template.html │ ├── preview │ │ ├── base.css │ │ ├── empty.html │ │ ├── error.html │ │ ├── save-first.html │ │ └── select-first.html │ └── vimr └── VimRTests │ ├── IgnoreServiceTest.swift │ ├── Info.plist │ └── Resources │ └── ignore-service-test │ ├── .gitignore │ └── a │ ├── .gitignore │ └── aa │ └── aaa │ └── .gitignore ├── Workspace ├── .gitignore ├── Package.swift ├── README.md ├── Sources │ └── Workspace │ │ ├── InnterToolBar.swift │ │ ├── ProxyWorkspaceBar.swift │ │ ├── Workspace.swift │ │ ├── WorkspaceBar.swift │ │ ├── WorkspaceTool.swift │ │ └── WorkspaceToolButton.swift └── Tests │ └── WorkspaceTests │ └── WorkspaceTests.swift ├── appcast.xml ├── appcast_snapshot.xml ├── bin ├── .python-version ├── README.md ├── build_jenkins.sh ├── build_nightly_jenkins.sh ├── build_nvimserver.sh ├── build_release.sh ├── build_vimr.sh ├── generate_autocmds.py ├── generate_cursor_shape.py ├── generate_sources.sh ├── neovim │ ├── bin │ │ ├── .gitignore │ │ ├── build_neovim.sh │ │ ├── build_neovim_for_dev.sh │ │ └── build_universal_neovim.sh │ └── resources │ │ ├── NvimServer.entitlements │ │ └── buildInfo.json ├── notarize_vimr.sh ├── requirements.txt ├── resources ├── set_appcast.py ├── set_new_versions.sh ├── setup_markdown_css.sh └── sign_vimr.sh ├── ci ├── README.md └── create_build_job.groovy ├── docs └── notes-on-cocoa-text-input.md └── resources ├── advanced-pref.png ├── appcast_template.xml ├── appearance-pref.png ├── autocmds.template.swift ├── copy-cli-tool-preferences.png ├── cursor_shape.template.swift ├── ligatures-preferences.png ├── release-notes.md ├── screenshot1.png ├── screenshot2.png ├── shortcuts-pref.png ├── snapshot-update.png └── vimr-app-icon.png /.gitattributes: -------------------------------------------------------------------------------- 1 | *.dylib filter=lfs diff=lfs merge=lfs -text 2 | *.a filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [qvacua] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/build-universal-neovim.yml: -------------------------------------------------------------------------------- 1 | name: 'Universal Neovim' 2 | on: 3 | push: 4 | tags: 5 | # example: neovim-v0.10.0-20240601.102525 6 | - neovim-v[0-9]+.[0-9]+.[0-9]+-* 7 | 8 | jobs: 9 | macos: 10 | strategy: 11 | fail-fast: true 12 | matrix: 13 | runner: [ macos-13, macos-14 ] 14 | include: 15 | - runner: macos-13 16 | arch: x86_64 17 | - runner: macos-14 18 | arch: arm64 19 | runs-on: ${{ matrix.runner }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | # Perform a full checkout #13471 24 | fetch-depth: 0 25 | submodules: true 26 | - name: Install dependencies 27 | run: brew bundle 28 | 29 | - name: Build neovim 30 | run: clean=true ./bin/neovim/bin/build_neovim.sh 31 | 32 | - uses: actions/upload-artifact@v4 33 | with: 34 | name: nvim-macos-${{ matrix.arch }} 35 | path: Neovim/build/nvim-macos-${{ matrix.arch }}.tar.gz 36 | retention-days: 1 37 | 38 | publish: 39 | needs: [macos] 40 | runs-on: macos-14 41 | env: 42 | GH_REPO: ${{ github.repository }} 43 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | permissions: 45 | contents: write 46 | steps: 47 | # Must perform checkout first, since it deletes the target directory 48 | # before running, and would therefore delete the downloaded artifacts 49 | - uses: actions/checkout@v4 50 | with: 51 | fetch-depth: 0 52 | 53 | - uses: actions/download-artifact@v4 54 | 55 | - name: Install dependencies 56 | run: brew bundle 57 | 58 | - name: Set tag name env 59 | run: | 60 | TAG_NAME=${{ github.ref }} 61 | echo "TAG_NAME=${TAG_NAME#refs/tags/}" >> $GITHUB_ENV 62 | 63 | - name: Move downloaded artifacts 64 | run: | 65 | mv nvim-macos-x86_64/* . 66 | mv nvim-macos-arm64/* . 67 | rm -r nvim-macos-x86_64 68 | rm -r nvim-macos-arm64 69 | 70 | - name: Create universal Neovim 71 | run: ./bin/neovim/bin/build_universal_neovim.sh 72 | 73 | # Set as prerelease such that the latest VimR release is marked as the latest stable release 74 | - name: Publish release 75 | run: | 76 | gh release create $TAG_NAME \ 77 | --prerelease \ 78 | --title "Universal ${TAG_NAME}" \ 79 | --notes "Neovim universal build with `libintl`, not signed." \ 80 | --target $GITHUB_SHA nvim-macos-x86_64.tar.gz nvim-macos-arm64.tar.gz nvim-macos-universal.tar.bz 81 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Neovim"] 2 | path = Neovim 3 | url = https://github.com/neovim/neovim.git 4 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --swiftversion 5.9 2 | 3 | --exclude Carthage,third-party,**/*.template.swift 4 | 5 | --indent 2 6 | --maxwidth 100 7 | 8 | --self insert 9 | --wraparguments before-first 10 | --ranges no-space 11 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | only_rules: 2 | - colon 3 | - fatal_error_message 4 | - force_cast 5 | - force_try 6 | - force_unwrapping 7 | - implicitly_unwrapped_optional 8 | - legacy_cggeometry_functions 9 | - legacy_constant 10 | - legacy_constructor 11 | - legacy_nsgeometry_functions 12 | - operator_usage_whitespace 13 | - redundant_string_enum_value 14 | - return_arrow_whitespace 15 | - trailing_newline 16 | - type_name 17 | - unused_optional_binding 18 | - vertical_whitespace 19 | - void_return 20 | - custom_rules 21 | 22 | excluded: 23 | - Carthage 24 | - .build 25 | - .deps 26 | - build 27 | - NvimView/Sources/NvimView/NvimAutoCommandEvent.generated.swift 28 | - NvimView/Sources/NvimView/NvimCursorModeShape.generated.swift 29 | - RxPack/Sources/RxPack/RxNeovimApi.generated.swift 30 | 31 | colon: 32 | apply_to_dictionaries: false 33 | 34 | indentation: 2 35 | 36 | custom_rules: 37 | no_objcMembers: 38 | name: "@objcMembers" 39 | regex: "@objcMembers" 40 | message: "Explicitly use @objc on each member you want to expose to Objective-C" 41 | severity: error 42 | no_direct_standard_out_logs: 43 | name: "Writing log messages directly to standard out is disallowed" 44 | regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" 45 | match_kinds: 46 | - identifier 47 | message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" 48 | severity: warning 49 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | brew 'coreutils' 2 | brew 'gnu-sed' 3 | brew 'libtool' 4 | brew 'automake' 5 | brew 'cmake' 6 | brew 'pkg-config' 7 | brew 'gettext' 8 | brew 'ninja' 9 | brew 'coreutils' 10 | 11 | brew 'python3' 12 | -------------------------------------------------------------------------------- /Commons/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /Commons/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Commons", 7 | platforms: [.macOS(.v12)], 8 | products: [ 9 | .library(name: "Commons", targets: ["Commons", "CommonsObjC"]), 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/Quick/Nimble", from: "13.4.0"), 13 | ], 14 | targets: [ 15 | .target(name: "Commons", dependencies: []), 16 | .target(name: "CommonsObjC", dependencies: []), 17 | .testTarget( 18 | name: "CommonsTests", 19 | dependencies: ["Commons", "Nimble"], 20 | resources: [ 21 | .copy("Resources"), 22 | ] 23 | ), 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /Commons/README.md: -------------------------------------------------------------------------------- 1 | # Commons 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /Commons/Sources/Commons/ConditionVariable.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | public final class ConditionVariable { 9 | private(set) var posted: Bool 10 | 11 | public init(posted: Bool = false) { 12 | self.posted = posted 13 | } 14 | 15 | public func wait(for seconds: TimeInterval, then fn: (() -> Void)? = nil) { 16 | self.condition.lock() 17 | defer { self.condition.unlock() } 18 | 19 | while !self.posted { 20 | self.condition.wait(until: Date(timeIntervalSinceNow: seconds)) 21 | self.posted = true 22 | } 23 | 24 | fn?() 25 | } 26 | 27 | public func broadcast(then fn: (() -> Void)? = nil) { 28 | self.condition.lock() 29 | defer { self.condition.unlock() } 30 | 31 | self.posted = true 32 | self.condition.broadcast() 33 | 34 | fn?() 35 | } 36 | 37 | private let condition = NSCondition() 38 | } 39 | -------------------------------------------------------------------------------- /Commons/Sources/Commons/CoreCommons.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | public extension CFRange { 9 | static let zero = CFRange(location: 0, length: 0) 10 | } 11 | 12 | public extension CGSize { 13 | func scaling(_ factor: CGFloat) -> CGSize { 14 | CGSize(width: self.width * factor, height: self.height * factor) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Commons/Sources/Commons/Defs.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | enum Defs { 9 | static let loggerSubsystem = "com.qvacua.Commons" 10 | 11 | enum LoggerCategory { 12 | static let general = "general" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Commons/Sources/Commons/FifoCache.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | public final class FifoCache { 9 | public init(count: Int) { 10 | self.count = count 11 | self.keyWriteIndex = 0 12 | self.keys = Array(repeating: nil, count: count) 13 | self.storage = Dictionary(minimumCapacity: count) 14 | } 15 | 16 | public func set(_ value: Value, forKey key: Key) { 17 | if let keyToDel = self.keys[self.keyWriteIndex] { self.storage.removeValue(forKey: keyToDel) } 18 | 19 | self.keys[self.keyWriteIndex] = key 20 | self.storage[key] = value 21 | 22 | self.keyWriteIndex = (self.keyWriteIndex + 1) % self.count 23 | } 24 | 25 | public func valueForKey(_ key: Key) -> Value? { self.storage[key] } 26 | 27 | public func clear() { 28 | self.keys = Array(repeating: nil, count: count) 29 | self.storage.removeAll(keepingCapacity: true) 30 | } 31 | 32 | private let count: Int 33 | private var keys: [Key?] 34 | private var keyWriteIndex: Int 35 | private var storage: [Key: Value] 36 | } 37 | -------------------------------------------------------------------------------- /Commons/Sources/CommonsObjC/NetUtils.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Greg Omelaenko - http://omelaen.co 3 | * Tae Won Ha - http://taewon.de - @hataewon 4 | * See LICENSE 5 | */ 6 | 7 | #import "NetUtils.h" 8 | #import 9 | #import 10 | #import 11 | 12 | static os_log_t logger; 13 | 14 | @implementation NetUtils 15 | 16 | // from https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/UsingSocketsandSocketStreams.html#//apple_ref/doc/uid/CH73-SW9 17 | // and http://stackoverflow.com/a/20850182/6939513 18 | // slightly modified 19 | + (in_port_t)openPort { 20 | static dispatch_once_t token; 21 | dispatch_once(&token, ^{ 22 | // See Defs.swift 23 | logger = os_log_create("com.qvacua.VimR", "general"); 24 | }); 25 | 26 | const int sock = socket(AF_INET, SOCK_STREAM, 0); 27 | if (sock < 0) { 28 | os_log_error(logger, "Could not open socket"); 29 | return 0; 30 | } 31 | 32 | struct sockaddr_in sin; 33 | memset(&sin, 0, sizeof(sin)); 34 | 35 | sin.sin_len = sizeof(sin); 36 | sin.sin_family = AF_INET; 37 | sin.sin_port = htons(0); 38 | 39 | if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) { 40 | if (errno == EADDRINUSE) { 41 | os_log_error(logger, "the port is not available."); 42 | return 0; 43 | } else { 44 | os_log_error( 45 | logger, 46 | "could not bind to process (%{public}d) %{public}s", 47 | errno, 48 | strerror(errno) 49 | ); 50 | return 0; 51 | } 52 | } 53 | 54 | socklen_t len = sizeof(sin); 55 | if (getsockname(sock, (struct sockaddr *) &sin, &len) == -1) { 56 | os_log_error(logger, "getsockname failed."); 57 | return 0; 58 | } 59 | 60 | const in_port_t result = ntohs(sin.sin_port); 61 | 62 | if (close(sock) < 0) { 63 | os_log_error(logger, "socket did not close: %{public}s", strerror(errno)); 64 | return 0; 65 | } 66 | 67 | return result; 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /Commons/Sources/CommonsObjC/include/NetUtils.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Greg Omelaenko - http://omelaen.co 3 | * Tae Won Ha - http://taewon.de - @hataewon 4 | * See LICENSE 5 | */ 6 | 7 | #import 8 | 9 | 10 | @interface NetUtils : NSObject 11 | 12 | + (in_port_t)openPort; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Commons/Support/EnvVarTest/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | import Commons 8 | 9 | @main 10 | class AppDelegate: NSObject, NSApplicationDelegate { 11 | @IBOutlet var window: NSWindow! 12 | @IBOutlet var textView: NSTextView! 13 | 14 | func applicationDidFinishLaunching(_: Notification) { 15 | let selfEnv = ProcessInfo.processInfo.environment 16 | let shellUrl = URL(fileURLWithPath: selfEnv["SHELL"] ?? "/bin/bash") 17 | let env = ProcessUtils.envVars(of: shellUrl, usingInteractiveMode: false) 18 | 19 | for (k, v) in env { 20 | let str = NSAttributedString(string: "\(k): \(v)\n") 21 | print(str) 22 | self.textView.textStorage?.append(str) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Commons/Support/EnvVarTest/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Commons/Support/EnvVarTest/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Commons/Support/EnvVarTest/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Commons/Support/EnvVarTest/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainNibFile 26 | MainMenu 27 | NSPrincipalClass 28 | NSApplication 29 | 30 | 31 | -------------------------------------------------------------------------------- /Commons/Tests/CommonsTests/DictionaryCommonsTest.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Nimble 7 | import XCTest 8 | 9 | class DictionaryCommonsTest: XCTestCase { 10 | func testMapToDict() { 11 | let dict = [ 12 | 1: "a", 13 | 2: "b", 14 | 3: "c", 15 | ] 16 | expect(dict.mapToDict { k, v in (v, "\(k)-\(v)") }).to(equal( 17 | [ 18 | "a": "1-a", 19 | "b": "2-b", 20 | "c": "3-c", 21 | ] 22 | )) 23 | } 24 | 25 | func testFlatMapToDict() { 26 | let dict = [ 27 | 1: "a", 28 | 2: "b", 29 | 3: "c", 30 | ] 31 | expect(dict.flatMapToDict { k, v in 32 | if k == 2 { 33 | return nil 34 | } 35 | 36 | return (v, "\(k)-\(v)") 37 | }).to(equal( 38 | [ 39 | "a": "1-a", 40 | "c": "3-c", 41 | ] 42 | )) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Commons/Tests/CommonsTests/FifoCacheTest.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Nimble 7 | import XCTest 8 | 9 | @testable import Commons 10 | 11 | class FifoCacheTest: XCTestCase { 12 | var fifo: FifoCache! 13 | 14 | override func setUp() { 15 | super.setUp() 16 | self.fifo = FifoCache(count: 10, queueQos: .default) 17 | } 18 | 19 | func testSimpleGet() { 20 | for i in 0...5 { self.fifo.set(i, forKey: i) } 21 | 22 | for i in 0...5 { expect(self.fifo.valueForKey(i)).to(equal(i)) } 23 | for i in 6..<10 { expect(self.fifo.valueForKey(i)).to(beNil()) } 24 | } 25 | 26 | func testGet() { 27 | for i in 0..<(10 * 3) { self.fifo.set(i, forKey: i) } 28 | for i in 20..<30 { expect(self.fifo.valueForKey(i)).to(equal(i)) } 29 | expect(self.fifo.valueForKey(19)).to(beNil()) 30 | expect(self.fifo.valueForKey(30)).to(beNil()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Commons/Tests/CommonsTests/Resources/FileUtilsTest/a1/a1-file1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/Commons/Tests/CommonsTests/Resources/FileUtilsTest/a1/a1-file1 -------------------------------------------------------------------------------- /Commons/Tests/CommonsTests/Resources/FileUtilsTest/a1/a2/a1-a2-file1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/Commons/Tests/CommonsTests/Resources/FileUtilsTest/a1/a2/a1-a2-file1 -------------------------------------------------------------------------------- /Commons/Tests/CommonsTests/Resources/FileUtilsTest/b1/b1-file1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/Commons/Tests/CommonsTests/Resources/FileUtilsTest/b1/b1-file1 -------------------------------------------------------------------------------- /Commons/Tests/CommonsTests/Resources/UrlCommonsTest/.dot-hidden-file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/Commons/Tests/CommonsTests/Resources/UrlCommonsTest/.dot-hidden-file -------------------------------------------------------------------------------- /Commons/Tests/CommonsTests/Resources/UrlCommonsTest/dummy.rtfd/TXT.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\expandedcolortbl;;} 5 | \paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 6 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 7 | 8 | \f0\fs24 \cf0 Dummy RTFD} -------------------------------------------------------------------------------- /Commons/Tests/CommonsTests/StringCommonsTest.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Nimble 7 | import XCTest 8 | 9 | class StringCommonsTest: XCTestCase { 10 | func testWithoutPrefix() { 11 | expect("prefixAbc".without(prefix: "prefix")).to(equal("Abc")) 12 | expect("prefix".without(prefix: "prefix")).to(equal("")) 13 | expect("Abcprefix".without(prefix: "prefix")).to(equal("Abcprefix")) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DEVELOP.md: -------------------------------------------------------------------------------- 1 | ## How to develop 2 | 3 | VimR includes a stock Neovim. From Neovim `v0.10.0`, we provide pre-built universal Neovim, 4 | see for instance . 5 | In most cases, you can use the pre-built Neovim. 6 | Run the following 7 | 8 | ```bash 9 | clean=true for_dev=false ./bin/build_nvimserver.sh 10 | ``` 11 | 12 | to download and place the files in the appropriate places. 13 | Now, you can just *run* VimR target in Xcode. 14 | 15 | If you want to build Neovim locally, you can use 16 | 17 | ```bash 18 | clean=true for_dev=true ./bin/build_nvimserver.sh 19 | ``` 20 | 21 | Afterwards, you can run VimR target in Xcode. 22 | 23 | (This is used when generating source since we need some generated header files.) 24 | 25 | ### How to enable the Debug menu in Release build 26 | 27 | ```bash 28 | defaults write com.qvacua.VimR enable-debug-menu 1 29 | ``` 30 | 31 | ## How to release 32 | 33 | ### Neovim 34 | 35 | * Update Neovim and generate sources: 36 | ```bash 37 | clean=true use_committed_nvim=true ./bin/generate_sources.sh 38 | ``` 39 | Use `use_committed=false` if you want to use modified local Neovim submodule. 40 | * Commit and push. 41 | * Tag and push with the following 42 | ```bash 43 | version=neovim-vX.Y.Z-$(date "+%Y%m%d.%H%M%S"); git tag -a "${version}" -m "${version}"; git push origin "${version}" 44 | ``` 45 | * Github action will build universal binary + runtime and package it. 46 | * Update the version of Neovim in `/bin/neovim/resources/buildInfo.json` 47 | 48 | ### VimR 49 | 50 | * Set a new version of VimR via 51 | ```bash 52 | is_snapshot=true ./bin/set_new_versions.sh # for snapshot or 53 | is_snapshot=false marketing_version=0.38.3 ./bin/set_new_versions.sh # for release 54 | ``` 55 | and commit. This will create a `${bundle_version}-snapshot/release.sh` file to be used 56 | with `build_release.sh` and `release-notes.temp.md` for release notes. 57 | * Tag with the name 58 | - Snapshot: `snapshot/yyyymmdd.HHMMSS` 59 | - Release: `vX.Y.Z-yyyymmdd.HHMMSS` 60 | * Push 61 | * Add release notes to `release-notes.temp.md`. 62 | * Build, package and upload via 63 | ```bash 64 | create_gh_release=true upload=true update_appcast=true \ 65 | release_spec_file=....sh \ 66 | ./bin/build_release.sh 67 | ``` 68 | * The `appcast{-snapshot}.xml` file is modified. Check and push. 69 | -------------------------------------------------------------------------------- /Ignore/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Ignore", 7 | platforms: [.macOS(.v12)], 8 | products: [ 9 | .library(name: "Ignore", targets: ["Ignore"]), 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/qvacua/misc.swift", exact: "0.2.0"), 13 | .package(url: "https://github.com/Quick/Nimble", from: "13.4.0"), 14 | ], 15 | targets: [ 16 | .target(name: "Ignore", dependencies: [.product(name: "WildmatchC", package: "misc.swift")]), 17 | .testTarget( 18 | name: "IgnoreTests", 19 | dependencies: ["Ignore", "Nimble"], 20 | resources: [.copy("Resources")] 21 | ), 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /Ignore/README.md: -------------------------------------------------------------------------------- 1 | # Ignore 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /Ignore/Sources/Ignore/GitUtils.swift: -------------------------------------------------------------------------------- 1 | /// Tae Won Ha - http://taewon.de - @hataewon 2 | /// See LICENSE 3 | 4 | import Foundation 5 | 6 | public enum GitUtils { 7 | static func globalGitignoreFileUrl() -> URL? { 8 | guard let path = shellCommandOutput( 9 | "git config --get core.excludesFile", 10 | workingDirectory: fm.homeDirectoryForCurrentUser 11 | ), 12 | FileManager.default.fileExists(atPath: path) 13 | else { return nil } 14 | 15 | return URL(fileURLWithPath: path) 16 | } 17 | 18 | static func gitDirInfoExcludeUrl(base: URL, gitRoot: URL? = nil) -> URL? { 19 | guard let gitRoot = gitRoot == nil ? gitRootUrl(base: base) : gitRoot, 20 | let gitDirName = shellCommandOutput("git rev-parse --git-dir", workingDirectory: gitRoot) 21 | else { return nil } 22 | 23 | let url = gitRoot.appendingPathComponent("\(gitDirName)/info/exclude") 24 | guard fm.fileExists(atPath: url.path) else { return nil } 25 | 26 | return url 27 | } 28 | 29 | static func gitRootUrl(base: URL) -> URL? { 30 | guard let path = shellCommandOutput("git rev-parse --show-toplevel", workingDirectory: base) 31 | else { return nil } 32 | 33 | return URL(fileURLWithPath: path, isDirectory: true) 34 | } 35 | 36 | private static func shellCommandOutput(_ command: String, workingDirectory: URL) -> String? { 37 | let task = Process() 38 | let pipe = Pipe() 39 | 40 | task.currentDirectoryURL = workingDirectory 41 | task.standardInput = nil 42 | task.standardOutput = pipe 43 | task.standardError = nil 44 | task.executableURL = URL(fileURLWithPath: "/bin/sh") 45 | task.arguments = ["-c", command] 46 | 47 | do { 48 | try task.run() 49 | task.waitUntilExit() 50 | } catch { 51 | return nil 52 | } 53 | 54 | guard task.terminationStatus == 0 else { return nil } 55 | guard let output = String( 56 | data: pipe.fileHandleForReading.readDataToEndOfFile(), 57 | encoding: .utf8 58 | ) else { return nil } 59 | 60 | let result = output.trimmingCharacters(in: .whitespacesAndNewlines) 61 | if result.isEmpty { return nil } else { return result } 62 | } 63 | } 64 | 65 | private let fm = FileManager.default 66 | -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/FileLineReaderTest.swift: -------------------------------------------------------------------------------- 1 | /// Tae Won Ha - http://taewon.de - @hataewon 2 | /// See LICENSE 3 | 4 | @testable import Ignore 5 | import Nimble 6 | import XCTest 7 | 8 | private struct TestSpec { 9 | var fileName: String 10 | var result: [String] 11 | } 12 | 13 | private let specs = [ 14 | TestSpec(fileName: "empty", result: []), 15 | TestSpec(fileName: "unix-only-new-lines", result: ["\n", "\n", "\n"]), 16 | TestSpec(fileName: "unix-no-line-ending-at-the-end", result: ["0123\n", "하태원\n", "abcde"]), 17 | TestSpec(fileName: "unix-with-line-ending-at-the-end", result: ["0123\n", "하태원\n", "abcde\n"]), 18 | TestSpec(fileName: "dos-only-new-lines", result: ["\r\n", "\r\n", "\r\n"]), 19 | TestSpec(fileName: "dos-no-line-ending-at-the-end", result: ["0123\r\n", "하태원\r\n", "abcde"]), 20 | TestSpec( 21 | fileName: "dos-with-line-ending-at-the-end", 22 | result: ["0123\r\n", "하태원\r\n", "abcde\r\n"] 23 | ), 24 | ] 25 | 26 | final class FileLineReaderTest: XCTestCase { 27 | func testSpecsDefaultBuffer() { 28 | specs.forEach { spec in 29 | let url = Bundle.module.url( 30 | forResource: spec.fileName, 31 | withExtension: "txt", 32 | subdirectory: "Resources/FileLineReaderTest" 33 | )! 34 | let lineReader = FileLineReader(url: url, encoding: .utf8) 35 | let lines = Array(lineReader) 36 | 37 | expect(lines).to(equal(spec.result)) 38 | } 39 | } 40 | 41 | func testSpecsSmallBuffer() { 42 | specs.forEach { spec in 43 | let url = Bundle.module.url( 44 | forResource: spec.fileName, 45 | withExtension: "txt", 46 | subdirectory: "Resources/FileLineReaderTest" 47 | )! 48 | let lineReader = FileLineReader(url: url, encoding: .utf8, lineBufferCount: 5) 49 | let lines = Array(lineReader) 50 | 51 | expect(lines).to(equal(spec.result)) 52 | } 53 | } 54 | 55 | func testSpecsBigBuffer() { 56 | specs.forEach { spec in 57 | let url = Bundle.module.url( 58 | forResource: spec.fileName, 59 | withExtension: "txt", 60 | subdirectory: "Resources/FileLineReaderTest" 61 | )! 62 | let lineReader = FileLineReader(url: url, encoding: .utf8, lineBufferCount: 2048) 63 | let lines = Array(lineReader) 64 | 65 | expect(lines).to(equal(spec.result)) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/IgnoreSplitTest.swift: -------------------------------------------------------------------------------- 1 | /// Tae Won Ha - http://taewon.de - @hataewon 2 | /// See LICENSE 3 | 4 | @testable import Ignore 5 | import Nimble 6 | import XCTest 7 | 8 | private struct IgnoreSplittingTestSpec { 9 | var fileName: String 10 | var mixed: [String] 11 | var disallow: [String] 12 | } 13 | 14 | private let ignoreSplittingTestSpecs = [ 15 | IgnoreSplittingTestSpec( 16 | fileName: "ignore-splitting-0", 17 | mixed: [], 18 | disallow: ["*.a", "*.b", "*.c", "*.d"] 19 | ), 20 | IgnoreSplittingTestSpec( 21 | fileName: "ignore-splitting-1", 22 | mixed: ["*.a"], 23 | disallow: ["*.b", "*.c", "*.d"] 24 | ), 25 | IgnoreSplittingTestSpec( 26 | fileName: "ignore-splitting-2", 27 | mixed: ["*.a", "*.b"], 28 | disallow: ["*.c", "*.d"] 29 | ), 30 | IgnoreSplittingTestSpec( 31 | fileName: "ignore-splitting-3", 32 | mixed: ["*.a", "*.b", "*.c"], 33 | disallow: ["*.d"] 34 | ), 35 | IgnoreSplittingTestSpec( 36 | fileName: "ignore-splitting-4", 37 | mixed: ["*.a", "*.b", "*.c", "*.d"], 38 | disallow: [] 39 | ), 40 | ] 41 | 42 | final class IgnoreSplitTest: XCTestCase { 43 | func testIgnoreSplitting() { 44 | ignoreSplittingTestSpecs.forEach { spec in 45 | let url = Bundle.module.url( 46 | forResource: "IgnoreCollectionTest", 47 | withExtension: nil, 48 | subdirectory: "Resources" 49 | )! 50 | let ignoreFile = Ignore(base: url, parent: nil, ignoreFileNames: [spec.fileName])! 51 | 52 | expect(ignoreFile.mixedIgnores.map(\.pattern)).to(equal(spec.mixed)) 53 | expect(ignoreFile.remainingDisallowIgnores.map(\.pattern)).to(equal(spec.disallow)) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/IgnoreTest.swift: -------------------------------------------------------------------------------- 1 | /// Tae Won Ha - http://taewon.de - @hataewon 2 | /// See LICENSE 3 | 4 | @testable import Ignore 5 | import Nimble 6 | import XCTest 7 | 8 | final class IgnoreTest: XCTestCase { 9 | func testIgnoreSplitting() { 10 | let root = Bundle.module.url( 11 | forResource: "IgnoreCollectionTest", 12 | withExtension: nil, 13 | subdirectory: "Resources" 14 | )! 15 | let c = Ignore(base: root, parent: nil)! 16 | 17 | expect(c.excludes(self.url("out", base: root, isDir: true))).to(beTrue()) 18 | expect(c.excludes(self.url("out", base: root, isDir: false))).to(beFalse()) 19 | expect(c.excludes(self.url("logs", base: root, isDir: true))).to(beTrue()) 20 | expect(c.excludes(self.url("logs", base: root, isDir: false))).to(beFalse()) 21 | 22 | expect(c.excludes(self.url("a.png", base: root))).to(beTrue()) 23 | 24 | expect(c.excludes(self.url("include-me", base: root))).to(beFalse()) 25 | expect(c.excludes(self.url("a/b/include-me", base: root))).to(beFalse()) 26 | 27 | expect(c.excludes(self.url("ignore-me", base: root))).to(beTrue()) 28 | expect(c.excludes(self.url("a/b/ignore-me", base: root))).to(beTrue()) 29 | } 30 | 31 | private func url(_ path: String, base: URL, isDir: Bool = false) -> URL { 32 | URL(fileURLWithPath: path, isDirectory: isDir, relativeTo: base) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/FileLineReaderTest/dos-no-line-ending-at-the-end.txt: -------------------------------------------------------------------------------- 1 | 0123 2 | 하태원 3 | abcde -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/FileLineReaderTest/dos-only-new-lines.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/FileLineReaderTest/dos-with-line-ending-at-the-end.txt: -------------------------------------------------------------------------------- 1 | 0123 2 | 하태원 3 | abcde 4 | -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/FileLineReaderTest/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/Ignore/Tests/IgnoreTests/Resources/FileLineReaderTest/empty.txt -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/FileLineReaderTest/unix-no-line-ending-at-the-end.txt: -------------------------------------------------------------------------------- 1 | 0123 2 | 하태원 3 | abcde -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/FileLineReaderTest/unix-only-new-lines.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/FileLineReaderTest/unix-with-line-ending-at-the-end.txt: -------------------------------------------------------------------------------- 1 | 0123 2 | 하태원 3 | abcde 4 | -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/IgnoreCollectionTest/.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | 3 | # will be included below 4 | include-me 5 | 6 | vendor/ 7 | **/ignore-me 8 | 9 | -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/IgnoreCollectionTest/.ignore: -------------------------------------------------------------------------------- 1 | # include include-me file 2 | !include-me 3 | 4 | /logs/ 5 | /out/ 6 | -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/IgnoreCollectionTest/ignore-splitting-0: -------------------------------------------------------------------------------- 1 | *.a 2 | *.b 3 | *.c 4 | *.d -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/IgnoreCollectionTest/ignore-splitting-1: -------------------------------------------------------------------------------- 1 | !*.a 2 | *.b 3 | *.c 4 | *.d -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/IgnoreCollectionTest/ignore-splitting-2: -------------------------------------------------------------------------------- 1 | *.a 2 | !*.b 3 | *.c 4 | *.d -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/IgnoreCollectionTest/ignore-splitting-3: -------------------------------------------------------------------------------- 1 | !*.a 2 | *.b 3 | !*.c 4 | *.d -------------------------------------------------------------------------------- /Ignore/Tests/IgnoreTests/Resources/IgnoreCollectionTest/ignore-splitting-4: -------------------------------------------------------------------------------- 1 | *.a 2 | *.b 3 | *.c 4 | !*.d -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025 Tae Won Ha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /NvimView/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | 7 | Sources/NvimView/Resources/runtime 8 | Sources/NvimView/Resources/NvimServer 9 | -------------------------------------------------------------------------------- /NvimView/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "NvimView", 7 | platforms: [.macOS(.v12)], 8 | products: [ 9 | .library(name: "NvimView", targets: ["NvimView"]), 10 | ], 11 | dependencies: [ 12 | .package(name: "RxPack", path: "../RxPack"), 13 | .package(url: "https://github.com/qvacua/MessagePack.swift", from: "4.1.0"), 14 | .package(url: "https://github.com/ReactiveX/RxSwift", from: "6.8.0"), 15 | .package(url: "https://github.com/Quick/Nimble", from: "13.4.0"), 16 | .package(name: "Commons", path: "../Commons"), 17 | .package(name: "Tabs", path: "../Tabs"), 18 | ], 19 | targets: [ 20 | .target( 21 | name: "NvimView", 22 | dependencies: [ 23 | .product(name: "RxSwift", package: "RxSwift"), 24 | .product(name: "RxPack", package: "RxPack"), 25 | "Tabs", 26 | .product(name: "RxNeovim", package: "RxPack"), 27 | .product(name: "MessagePack", package: "MessagePack.swift"), 28 | "Commons", 29 | ], 30 | // com.qvacua.NvimView.vim is copied by the build NvimServer script. 31 | exclude: ["Resources/com.qvacua.NvimView.vim"], 32 | resources: [ 33 | .copy("Resources/runtime"), 34 | .copy("Resources/NvimServer"), 35 | ] 36 | ), 37 | .testTarget(name: "NvimViewTests", dependencies: ["NvimView", "Nimble"]), 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /NvimView/README.md: -------------------------------------------------------------------------------- 1 | # NvimView 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /NvimView/Sources/NvimView/CellAttributesCollection.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class CellAttributesCollection { 9 | static let defaultAttributesId = 0 10 | static let reversedDefaultAttributesId = Int.max 11 | static let markedAttributesId = Int.max - 1 12 | 13 | private(set) var defaultAttributes = CellAttributes( 14 | fontTrait: [], 15 | foreground: 0, 16 | background: 0xFFFFFF, 17 | special: 0xFF0000, 18 | reverse: false 19 | ) 20 | 21 | init() { self.attributes[CellAttributesCollection.defaultAttributesId] = self.defaultAttributes } 22 | 23 | func attributes(of id: Int) -> CellAttributes? { 24 | self.attributes(of: id, withDefaults: self.defaultAttributes) 25 | } 26 | 27 | func attributes(of id: Int, withDefaults defaults: CellAttributes) -> CellAttributes? { 28 | if id == Self.markedAttributesId { 29 | var attr = self.defaultAttributes 30 | attr.fontTrait.formUnion(.underline) 31 | return attr 32 | } 33 | if id == Self.reversedDefaultAttributesId { return self.defaultAttributes.reversed } 34 | 35 | let absId = abs(id) 36 | guard let attrs = self.attributes[absId] else { return nil } 37 | if id < 0 { return attrs.replacingDefaults(with: self.defaultAttributes).reversed } 38 | 39 | return attrs.replacingDefaults(with: defaults) 40 | } 41 | 42 | func set(attributes: CellAttributes, for id: Int) { 43 | self.attributes[id] = attributes 44 | if id == CellAttributesCollection.defaultAttributesId { self.defaultAttributes = attributes } 45 | } 46 | 47 | private var attributes: [Int: CellAttributes] = [:] 48 | } 49 | -------------------------------------------------------------------------------- /NvimView/Sources/NvimView/ColorUtils.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | import Commons 8 | 9 | final class ColorUtils { 10 | /// ARGB 11 | static func cgColorIgnoringAlpha(_ rgb: Int) -> CGColor { 12 | if let color = cgColorCache.valueForKey(rgb) { return color } 13 | 14 | let color = self.colorIgnoringAlpha(rgb).cgColor 15 | cgColorCache.set(color, forKey: rgb) 16 | 17 | return color 18 | } 19 | 20 | static func cgColorIgnoringAlpha(_ rgb: Int32) -> CGColor { 21 | if let color = cgColorCache.valueForKey(Int(rgb)) { return color } 22 | 23 | let color = self.colorIgnoringAlpha(Int(rgb)).cgColor 24 | cgColorCache.set(color, forKey: Int(rgb)) 25 | 26 | return color 27 | } 28 | 29 | /// ARGB 30 | static func colorIgnoringAlpha(_ rgb: Int) -> NSColor { 31 | if let color = colorCache.valueForKey(rgb) { return color } 32 | 33 | // @formatter:off 34 | let red = ((rgb >> 16) & 0xFF).cgf / 255.0 35 | let green = ((rgb >> 8) & 0xFF).cgf / 255.0 36 | let blue = (rgb & 0xFF).cgf / 255.0 37 | // @formatter:on 38 | 39 | let color = NSColor(srgbRed: red, green: green, blue: blue, alpha: 1.0) 40 | colorCache.set(color, forKey: rgb) 41 | 42 | return color 43 | } 44 | } 45 | 46 | private let colorCache = FifoCache(count: 500) 47 | private let cgColorCache = FifoCache(count: 500) 48 | -------------------------------------------------------------------------------- /NvimView/Sources/NvimView/Defs.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | enum Defs { 9 | static let loggerSubsystem = "com.qvacua.NvimView" 10 | 11 | enum LoggerCategory { 12 | static let bridge = "bridge" 13 | static let view = "view" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /NvimView/Sources/NvimView/Geometry.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | public struct Position: CustomStringConvertible, Equatable { 9 | public static let zero = Position(row: 0, column: 0) 10 | public static let null = Position(row: -1, column: -1) 11 | 12 | // FIXME: GH-666: Delete 13 | public static let beginning = Position(row: 1, column: 1) 14 | 15 | public static func == (left: Position, right: Position) -> Bool { 16 | if left.row != right.row { return false } 17 | if left.column != right.column { return false } 18 | 19 | return true 20 | } 21 | 22 | public var row: Int 23 | public var column: Int 24 | 25 | public init(row: Int, column: Int) { 26 | self.row = row 27 | self.column = column 28 | } 29 | 30 | public var description: String { "Position<\(self.row):\(self.column)>" } 31 | 32 | public func advancing(row dy: Int, column dx: Int) -> Position { 33 | Position(row: self.row + dy, column: self.column + dx) 34 | } 35 | } 36 | 37 | struct Size: CustomStringConvertible, Equatable { 38 | static let zero = Size(width: 0, height: 0) 39 | 40 | static func == (left: Size, right: Size) -> Bool { 41 | left.width == right.width && left.height == right.height 42 | } 43 | 44 | var width: Int 45 | var height: Int 46 | 47 | var description: String { "Size<\(self.width):\(self.height)>" } 48 | } 49 | 50 | struct Region: CustomStringConvertible { 51 | static let zero = Region(top: 0, bottom: 0, left: 0, right: 0) 52 | 53 | var top: Int 54 | var bottom: Int 55 | var left: Int 56 | var right: Int 57 | 58 | var description: String { 59 | "Region<\(self.top)...\(self.bottom):\(self.left)...\(self.right)>" 60 | } 61 | 62 | var rowRange: ClosedRange { self.top...self.bottom } 63 | 64 | var columnRange: ClosedRange { self.left...self.right } 65 | } 66 | -------------------------------------------------------------------------------- /NvimView/Sources/NvimView/MessagePackCommons.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | import MessagePack 8 | 9 | extension MessagePackValue { 10 | var intValue: Int? { 11 | guard let i64 = self.int64Value else { return nil } 12 | 13 | return Int(i64) 14 | } 15 | } 16 | 17 | enum MessagePackUtils { 18 | static func value(from data: Data?, conversion: (MessagePackValue) -> T?) -> T? { 19 | guard let d = data else { return nil } 20 | 21 | do { return try conversion(unpack(d).value) } 22 | catch { return nil } 23 | } 24 | 25 | static func value(from v: MessagePackValue, conversion: (MessagePackValue) -> T?) -> T? { 26 | conversion(v) 27 | } 28 | 29 | static func array( 30 | from value: MessagePackValue, ofSize size: Int, 31 | conversion: (MessagePackValue) -> T? 32 | ) -> [T]? { 33 | guard let array = value.arrayValue else { return nil } 34 | guard array.count == size else { return nil } 35 | 36 | return array.compactMap(conversion) 37 | } 38 | 39 | static func value(from data: Data?) -> MessagePackValue? { 40 | guard let d = data else { return nil } 41 | 42 | do { return try unpack(d).value } 43 | catch { return nil } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /NvimView/Sources/NvimView/ModeInfo.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Johann Rudloff - @cypheon 3 | * See LICENSE 4 | */ 5 | 6 | import MessagePack 7 | 8 | public enum CursorShape: Equatable { 9 | case block 10 | case horizontal(cellPercentage: Int) 11 | case vertical(cellPercentage: Int) 12 | 13 | static func of(shape: String, cellPercentage: Int?) -> CursorShape? { 14 | switch shape { 15 | case "block": block 16 | case "horizontal": cellPercentage.map(horizontal(cellPercentage:)) 17 | case "vertical": cellPercentage.map(vertical(cellPercentage:)) 18 | default: nil 19 | } 20 | } 21 | } 22 | 23 | public struct ModeInfo: CustomStringConvertible { 24 | public let attrId: Int? 25 | public let cursorShape: CursorShape 26 | public let shortName: String 27 | public let name: String 28 | 29 | public init( 30 | withMsgPackDict dict: MessagePackValue 31 | ) { 32 | self.attrId = dict["attr_id"]?.intValue 33 | if let shapeName = dict["cursor_shape"]?.stringValue, 34 | let cursorShape = CursorShape.of( 35 | shape: shapeName, 36 | cellPercentage: dict["cell_percentage"]?.intValue 37 | ) 38 | { 39 | self.cursorShape = cursorShape 40 | } else { 41 | self.cursorShape = .block 42 | } 43 | self.shortName = dict["short_name"]?.stringValue ?? "?" 44 | self.name = dict["name"]?.stringValue ?? (dict["short_name"]?.stringValue ?? "???") 45 | } 46 | 47 | public var description: String { 48 | "ModeInfo<\(self.name) (\(self.shortName)) shape: \(self.cursorShape)" + 49 | "attr_id:\(String(describing: self.attrId))>" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /NvimView/Sources/NvimView/NvimCursorModeShape.generated.swift: -------------------------------------------------------------------------------- 1 | // Auto generated for nvim v0.11.2 2 | // See bin/generate_cursor_shape.py 3 | 4 | public enum CursorModeShape: String { 5 | case normal 6 | case visual 7 | case insert 8 | case replace 9 | case cmdlineNormal 10 | case cmdlineInsert 11 | case cmdlineReplace 12 | case operatorPending 13 | case visualExclusive 14 | case onCmdline 15 | case onStatusLine 16 | case draggingStatusLine 17 | case onVerticalSepLine 18 | case draggingVerticalSepLine 19 | case more 20 | case moreLastLine 21 | case showingMatchingParen 22 | case terminalMode 23 | case count 24 | } 25 | -------------------------------------------------------------------------------- /NvimView/Sources/NvimView/NvimView+Debug.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | public extension NvimView { 9 | @IBAction func debug1(_: Any?) { 10 | do { try self.ugrid.dump() } catch { self.log.error("Could not dump UGrid: \(error)") } 11 | } 12 | 13 | @IBAction func debug2(_: Any?) { 14 | self.log.debug("Nothing yet") 15 | } 16 | 17 | internal func draw(cellGridIn context: CGContext) { 18 | context.saveGState() 19 | defer { context.restoreGState() } 20 | 21 | let color = NSColor.magenta.cgColor 22 | context.setFillColor(color) 23 | 24 | let discreteSize = self.discreteSize(size: self.bounds.size) 25 | var lines = [ 26 | CGRect(x: 0 + self.offset.x, y: 0, width: 1, height: self.bounds.height), 27 | CGRect( 28 | x: self.bounds.width - 1 + self.offset.x, 29 | y: 0, 30 | width: 1, 31 | height: self.bounds.height 32 | ), 33 | CGRect( 34 | x: 0, 35 | y: self.bounds.height - 1 - self.offset.y, 36 | width: self.bounds.width, 37 | height: 1 38 | ), 39 | CGRect( 40 | x: 0, 41 | y: self.bounds.height - 1 - self.offset.y 42 | - discreteSize.height.cgf * self.self.cellSize.height, 43 | width: self.bounds.width, 44 | height: 1 45 | ), 46 | ] 47 | 48 | for row in 0...discreteSize.height { 49 | for col in 0...discreteSize.width { 50 | lines.append(contentsOf: [ 51 | CGRect( 52 | x: col.cgf * self.cellSize.width + self.offset.x - 1, 53 | y: 0, 54 | width: 1, 55 | height: self.bounds.height 56 | ), 57 | CGRect( 58 | x: 0, 59 | y: self.bounds.height - 1 60 | - self.offset.y - row.cgf * self.self.cellSize.height, 61 | width: self.bounds.width, 62 | height: 1 63 | ), 64 | ]) 65 | } 66 | } 67 | 68 | context.fill(lines) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /NvimView/Sources/NvimView/NvimView+Dragging.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | public extension NvimView { 9 | override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { 10 | isFile(sender: sender) ? .copy : NSDragOperation() 11 | } 12 | 13 | override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation { 14 | isFile(sender: sender) ? .copy : NSDragOperation() 15 | } 16 | 17 | override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { 18 | guard isFile(sender: sender) else { return false } 19 | 20 | guard let urls = sender.draggingPasteboard 21 | .readObjects(forClasses: [NSURL.self]) as? [URL] else { return false } 22 | 23 | self.open(urls: urls) 24 | .subscribe(on: self.scheduler) 25 | .subscribe(onError: { [weak self] error in 26 | self?.eventsSubject.onNext( 27 | .apiError(msg: "\(urls) could not be opened.", cause: error) 28 | ) 29 | }) 30 | .disposed(by: self.disposeBag) 31 | 32 | return true 33 | } 34 | } 35 | 36 | private func isFile(sender: NSDraggingInfo) -> Bool { 37 | (sender.draggingPasteboard.types?.contains(NSPasteboard.PasteboardType.fileURL)) ?? false 38 | } 39 | -------------------------------------------------------------------------------- /NvimView/Sources/NvimView/NvimView+Objects.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | import RxNeovim 8 | import RxPack 9 | 10 | public extension NvimView { 11 | struct Buffer: Equatable { 12 | public static func == (lhs: Buffer, rhs: Buffer) -> Bool { 13 | guard lhs.handle == rhs.handle else { return false } 14 | 15 | // Transient buffer active -> open a file -> the resulting buffer has the same handle, 16 | // but different URL 17 | return lhs.url == rhs.url 18 | } 19 | 20 | public let apiBuffer: RxNeovimApi.Buffer 21 | public let url: URL? 22 | public let type: String 23 | 24 | public let isDirty: Bool 25 | public let isCurrent: Bool 26 | public let isListed: Bool 27 | 28 | public var isTransient: Bool { 29 | if self.isDirty { return false } 30 | if self.url != nil { return false } 31 | 32 | return true 33 | } 34 | 35 | public var name: String? { 36 | if self.type == "quickfix" { return "Quickfix" } 37 | 38 | return self.url?.lastPathComponent 39 | } 40 | 41 | public var handle: Int { self.apiBuffer.handle } 42 | } 43 | 44 | struct Window { 45 | public let apiWindow: RxNeovimApi.Window 46 | public let buffer: Buffer 47 | public let isCurrentInTab: Bool 48 | 49 | public var handle: Int { self.apiWindow.handle } 50 | } 51 | 52 | struct Tabpage { 53 | public let apiTabpage: RxNeovimApi.Tabpage 54 | public let windows: [Window] 55 | public let isCurrent: Bool 56 | 57 | public var currentWindow: Window? { self.windows.first { $0.isCurrentInTab } } 58 | 59 | public var handle: Int { self.apiTabpage.handle } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /NvimView/Sources/NvimView/Resources/com.qvacua.NvimView.vim: -------------------------------------------------------------------------------- 1 | set mouse=a 2 | set title 3 | 4 | autocmd VimEnter,ColorScheme * :highlight default VimrDefaultCursor gui=reverse guibg=NONE guifg=NONE 5 | set guicursor=a:block-VimrDefaultCursor 6 | autocmd VimEnter,ColorScheme * :highlight default VimrInsertCursor guibg=fg 7 | set guicursor=i:ver25-VimrInsertCursor 8 | -------------------------------------------------------------------------------- /NvimView/Sources/NvimView/Runs.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | struct AttributesRun { 9 | var location: CGPoint 10 | var cells: ArraySlice 11 | var attrs: CellAttributes 12 | } 13 | 14 | struct FontGlyphRun { 15 | var font: NSFont 16 | var glyphs: [CGGlyph] 17 | var positions: [CGPoint] 18 | } 19 | -------------------------------------------------------------------------------- /NvimView/Support/DrawerDev/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | @main 9 | class AppDelegate: NSObject, NSApplicationDelegate { 10 | @IBOutlet var window: NSWindow! 11 | 12 | func applicationDidFinishLaunching(_: Notification) { 13 | // Insert code here to initialize your application 14 | } 15 | 16 | func applicationWillTerminate(_: Notification) { 17 | // Insert code here to tear down your application 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /NvimView/Support/DrawerDev/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /NvimView/Support/DrawerDev/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /NvimView/Support/DrawerDev/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | SNAPSHOT-357 21 | CFBundleVersion 22 | 357 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2018 Tae Won Ha. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /NvimView/Support/DrawerDev/NvimView.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | // Dummy NvimView class for FontUtils. 9 | class NvimView { 10 | static let defaultFont = NSFont.userFixedPitchFont(ofSize: 12)! 11 | static let minFontSize = CGFloat(9) 12 | static let maxFontSize = CGFloat(128) 13 | } 14 | -------------------------------------------------------------------------------- /NvimView/Support/DrawerPerf/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | import GameKit 8 | import os 9 | 10 | @main 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | @IBOutlet var window: NSWindow! 13 | var result = [[[FontGlyphRun]]](repeating: [], count: count) 14 | 15 | func applicationDidFinishLaunching(_: Notification) { 16 | var results = [CFTimeInterval]() 17 | let repeatCount = 5 18 | for _ in 0.. Void) -> CFTimeInterval { 47 | let start = CFAbsoluteTimeGetCurrent() 48 | body() 49 | return CFAbsoluteTimeGetCurrent() - start 50 | } 51 | } 52 | 53 | private let count = 500 54 | -------------------------------------------------------------------------------- /NvimView/Support/DrawerPerf/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "mac", 5 | "size": "16x16", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "mac", 10 | "size": "16x16", 11 | "scale": "2x" 12 | }, 13 | { 14 | "idiom": "mac", 15 | "size": "32x32", 16 | "scale": "1x" 17 | }, 18 | { 19 | "idiom": "mac", 20 | "size": "32x32", 21 | "scale": "2x" 22 | }, 23 | { 24 | "idiom": "mac", 25 | "size": "128x128", 26 | "scale": "1x" 27 | }, 28 | { 29 | "idiom": "mac", 30 | "size": "128x128", 31 | "scale": "2x" 32 | }, 33 | { 34 | "idiom": "mac", 35 | "size": "256x256", 36 | "scale": "1x" 37 | }, 38 | { 39 | "idiom": "mac", 40 | "size": "256x256", 41 | "scale": "2x" 42 | }, 43 | { 44 | "idiom": "mac", 45 | "size": "512x512", 46 | "scale": "1x" 47 | }, 48 | { 49 | "idiom": "mac", 50 | "size": "512x512", 51 | "scale": "2x" 52 | } 53 | ], 54 | "info": { 55 | "version": 1, 56 | "author": "xcode" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /NvimView/Support/DrawerPerf/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /NvimView/Support/DrawerPerf/FontTrait.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | public struct FontTrait: OptionSet { 9 | public let rawValue: UInt 10 | 11 | public init(rawValue: UInt) { 12 | self.rawValue = rawValue 13 | } 14 | 15 | static let italic = FontTrait(rawValue: 1 << 0) 16 | static let bold = FontTrait(rawValue: 1 << 1) 17 | static let underline = FontTrait(rawValue: 1 << 2) 18 | static let undercurl = FontTrait(rawValue: 1 << 3) 19 | } 20 | -------------------------------------------------------------------------------- /NvimView/Support/DrawerPerf/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | SNAPSHOT-357 21 | CFBundleVersion 22 | 357 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2019 Tae Won Ha. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /NvimView/Support/DrawerPerf/NvimView.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | // Dummy NvimView class for FontUtils. 9 | class NvimView { 10 | static let defaultFont = NSFont.userFixedPitchFont(ofSize: 12)! 11 | static let minFontSize = CGFloat(9) 12 | static let maxFontSize = CGFloat(128) 13 | } 14 | -------------------------------------------------------------------------------- /NvimView/Support/MinimalNvimViewDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | @main 9 | class AppDelegate: NSObject, NSApplicationDelegate { 10 | func applicationDidFinishLaunching(_: Notification) {} 11 | 12 | func applicationOpenUntitledFile(_: NSApplication) -> Bool { 13 | if openNewWindowWhenLaunching { false } else { true } 14 | } 15 | 16 | func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows _: Bool) -> Bool { 17 | false 18 | } 19 | 20 | func applicationShouldTerminate(_: NSApplication) -> NSApplication.TerminateReply { 21 | NSDocumentController.shared 22 | .documents 23 | .compactMap { $0 as? Document } 24 | .forEach { $0.quitWithoutSaving() } 25 | 26 | return .terminateNow 27 | } 28 | } 29 | 30 | private let openNewWindowWhenLaunching = false 31 | -------------------------------------------------------------------------------- /NvimView/Support/MinimalNvimViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /NvimView/Support/MinimalNvimViewDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /NvimView/Support/MinimalNvimViewDemo/Base.lproj/Document.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /NvimView/Support/MinimalNvimViewDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeExtensions 11 | 12 | mydoc 13 | 14 | CFBundleTypeIconFile 15 | 16 | CFBundleTypeName 17 | DocumentType 18 | CFBundleTypeOSTypes 19 | 20 | ???? 21 | 22 | CFBundleTypeRole 23 | Editor 24 | NSDocumentClass 25 | $(PRODUCT_MODULE_NAME).Document 26 | 27 | 28 | CFBundleExecutable 29 | $(EXECUTABLE_NAME) 30 | CFBundleIconFile 31 | 32 | CFBundleIdentifier 33 | $(PRODUCT_BUNDLE_IDENTIFIER) 34 | CFBundleInfoDictionaryVersion 35 | 6.0 36 | CFBundleName 37 | $(PRODUCT_NAME) 38 | CFBundlePackageType 39 | APPL 40 | CFBundleShortVersionString 41 | SNAPSHOT-357 42 | CFBundleVersion 43 | 357 44 | LSMinimumSystemVersion 45 | $(MACOSX_DEPLOYMENT_TARGET) 46 | NSHumanReadableCopyright 47 | Copyright © 2019 Tae Won Ha. All rights reserved. 48 | NSMainNibFile 49 | MainMenu 50 | NSPrincipalClass 51 | NSApplication 52 | 53 | 54 | -------------------------------------------------------------------------------- /NvimView/Support/NvimViewSupport.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NvimView/Support/NvimViewSupport.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /NvimView/Support/NvimViewSupport.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Socket", 6 | "repositoryURL": "https://github.com/IBM-Swift/BlueSocket", 7 | "state": { 8 | "branch": null, 9 | "revision": "c46a3d41f5b2401d18bcb46d0101cdc5cd13e307", 10 | "version": "1.0.52" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /NvimView/Tests/NvimViewTests/CellAttributesCollectionTest.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | import Nimble 8 | import XCTest 9 | 10 | @testable import NvimView 11 | 12 | class CellAttributesCollectionTest: XCTestCase { 13 | func testSetDefaultAttributes() { 14 | let attrs = CellAttributes( 15 | fontTrait: [], foreground: 1, background: 2, special: 3, reverse: true 16 | ) 17 | self.cellAttributesCollection.set(attributes: attrs, for: 0) 18 | expect(self.cellAttributesCollection.defaultAttributes) 19 | .to(equal(attrs)) 20 | } 21 | 22 | func testSetAndGetAttributes() { 23 | let attrs = CellAttributes( 24 | fontTrait: [], foreground: 1, background: 2, special: 3, reverse: true 25 | ) 26 | self.cellAttributesCollection.set(attributes: attrs, for: 1) 27 | expect(self.cellAttributesCollection.attributes(of: 1)) 28 | .to(equal(attrs)) 29 | } 30 | 31 | func testSetAndGetAttributesWithDefaults() { 32 | let defaultAttrs = CellAttributes( 33 | fontTrait: [], foreground: 10, background: 20, special: 30, reverse: true 34 | ) 35 | self.cellAttributesCollection 36 | .set(attributes: defaultAttrs, for: CellAttributesCollection.defaultAttributesId) 37 | 38 | var attrs = CellAttributes( 39 | fontTrait: [], foreground: -1, background: 2, special: 3, reverse: true 40 | ) 41 | self.cellAttributesCollection.set(attributes: attrs, for: 1) 42 | expect(self.cellAttributesCollection.attributes(of: 1)) 43 | .to(equal(CellAttributes( 44 | fontTrait: [], foreground: 20, background: 2, special: 3, reverse: true 45 | ))) 46 | 47 | attrs = CellAttributes( 48 | fontTrait: [], foreground: 1, background: -1, special: 3, reverse: true 49 | ) 50 | self.cellAttributesCollection.set(attributes: attrs, for: 1) 51 | expect(self.cellAttributesCollection.attributes(of: 1)) 52 | .to(equal(CellAttributes( 53 | fontTrait: [], foreground: 1, background: 10, special: 3, reverse: true 54 | ))) 55 | 56 | attrs = CellAttributes( 57 | fontTrait: [], foreground: 1, background: -1, special: -1, reverse: true 58 | ) 59 | self.cellAttributesCollection.set(attributes: attrs, for: 1) 60 | expect(self.cellAttributesCollection.attributes(of: 1)) 61 | .to(equal(CellAttributes( 62 | fontTrait: [], foreground: 1, background: 10, special: 30, reverse: true 63 | ))) 64 | } 65 | 66 | private let cellAttributesCollection = CellAttributesCollection() 67 | } 68 | -------------------------------------------------------------------------------- /NvimView/Tests/NvimViewTests/NimbleCommons.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | import Nimble 8 | 9 | // I don't know why the font returned by Typesetter is not equal to the font 10 | // it should be equal to. This is a workaround. 11 | func equalFont(_ expectedValue: NSFont?) -> Nimble.Predicate { 12 | Predicate { actualExpression in 13 | let msg = ExpectationMessage.expectedActualValueTo( 14 | "equal <\(String(describing: expectedValue))>" 15 | ) 16 | if let actualValue = try actualExpression.evaluate() { 17 | return PredicateResult( 18 | bool: NSFont( 19 | name: actualValue.fontName, 20 | size: actualValue.pointSize 21 | ) == expectedValue!, 22 | message: msg 23 | ) 24 | } else { 25 | return PredicateResult( 26 | status: .fail, 27 | message: msg.appendedBeNilHint() 28 | ) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /RxPack/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Tae Won Ha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /RxPack/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "cwlcatchexception", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/mattgallagher/CwlCatchException.git", 7 | "state" : { 8 | "revision" : "35f9e770f54ce62dd8526470f14c6e137cef3eea", 9 | "version" : "2.1.1" 10 | } 11 | }, 12 | { 13 | "identity" : "cwlpreconditiontesting", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git", 16 | "state" : { 17 | "revision" : "c21f7bab5ca8eee0a9998bbd17ca1d0eb45d4688", 18 | "version" : "2.1.0" 19 | } 20 | }, 21 | { 22 | "identity" : "messagepack.swift", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/a2/MessagePack.swift", 25 | "state" : { 26 | "revision" : "27b35fd49e92fcae395bf8ccb233499d89cc7890", 27 | "version" : "4.0.0" 28 | } 29 | }, 30 | { 31 | "identity" : "nimble", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/Quick/Nimble", 34 | "state" : { 35 | "revision" : "d616f15123bfb36db1b1075153f73cf40605b39d", 36 | "version" : "13.0.0" 37 | } 38 | }, 39 | { 40 | "identity" : "rxswift", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/ReactiveX/RxSwift", 43 | "state" : { 44 | "revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4", 45 | "version" : "6.6.0" 46 | } 47 | } 48 | ], 49 | "version" : 2 50 | } 51 | -------------------------------------------------------------------------------- /RxPack/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "RxPack", 7 | platforms: [.macOS(.v12)], 8 | products: [ 9 | .library(name: "RxPack", targets: ["RxPack"]), 10 | .library(name: "RxNeovim", targets: ["RxNeovim"]), 11 | ], 12 | dependencies: [ 13 | .package(url: "https://github.com/ReactiveX/RxSwift", from: "6.8.0"), 14 | .package(url: "https://github.com/qvacua/MessagePack.swift", .upToNextMinor(from: "4.1.0")), 15 | .package(url: "https://github.com/Quick/Nimble", from: "13.4.0"), 16 | ], 17 | targets: [ 18 | .target(name: "RxPack", dependencies: [ 19 | .product(name: "RxSwift", package: "RxSwift"), 20 | .product(name: "MessagePack", package: "MessagePack.swift"), 21 | ]), 22 | .testTarget(name: "RxPackTests", dependencies: [ 23 | "RxPack", 24 | .product(name: "RxBlocking", package: "RxSwift"), 25 | .product(name: "RxTest", package: "RxSwift"), 26 | .product(name: "Nimble", package: "Nimble"), 27 | ]), 28 | .target( 29 | name: "RxNeovim", 30 | dependencies: [ 31 | .product(name: "RxSwift", package: "RxSwift"), 32 | "RxPack", 33 | .product(name: "MessagePack", package: "MessagePack.swift"), 34 | ] 35 | ), 36 | .testTarget( 37 | name: "RxNeovimTests", 38 | dependencies: [ 39 | "RxNeovim", 40 | .product(name: "RxBlocking", package: "RxSwift"), 41 | .product(name: "Nimble", package: "Nimble"), 42 | ] 43 | ), 44 | ] 45 | ) 46 | -------------------------------------------------------------------------------- /RxPack/README.md: -------------------------------------------------------------------------------- 1 | To generate the API Swift file, first checkout the correct ref, e.g., 2 | develop or update-neovim. Then, 3 | 4 | ```bash 5 | ./bin/generate_sources.sh 6 | ``` 7 | 8 | -------------------------------------------------------------------------------- /RxPack/Sources/RxNeovim/RxNeovimApi.swift: -------------------------------------------------------------------------------- 1 | /// Tae Won Ha - http://taewon.de - @hataewon 2 | /// See LICENSE 3 | 4 | import Foundation 5 | import RxPack 6 | import RxSwift 7 | 8 | public final class RxNeovimApi { 9 | public enum Event { 10 | case error(msg: String) 11 | } 12 | 13 | public struct Buffer: Equatable, Hashable { 14 | public let handle: Int 15 | public init(_ handle: Int) { self.handle = handle } 16 | } 17 | 18 | public struct Window: Equatable, Hashable { 19 | public let handle: Int 20 | public init(_ handle: Int) { self.handle = handle } 21 | } 22 | 23 | public struct Tabpage: Equatable, Hashable { 24 | public let handle: Int 25 | public init(_ handle: Int) { self.handle = handle } 26 | } 27 | 28 | public typealias Value = RxMsgpackRpc.Value 29 | 30 | public var msgpackRawStream: Observable { self.msgpackRpc.stream } 31 | 32 | public func run(inPipe: Pipe, outPipe: Pipe, errorPipe: Pipe) -> Completable { 33 | self.msgpackRpc.run(inPipe: inPipe, outPipe: outPipe, errorPipe: errorPipe) 34 | } 35 | 36 | public func stop() -> Completable { self.msgpackRpc.stop() } 37 | 38 | public func checkBlocked(_ single: Single) -> Single { 39 | self 40 | .nvimGetMode() 41 | .flatMap { dict -> Single in 42 | guard (dict["blocking"]?.boolValue ?? false) == false else { 43 | throw RxNeovimApi.Error.blocked 44 | } 45 | 46 | return single 47 | } 48 | } 49 | 50 | public func sendRequest( 51 | method: String, 52 | params: [RxNeovimApi.Value] 53 | ) -> Single { 54 | self.msgpackRpc 55 | .request(method: method, params: params, expectsReturnValue: true) 56 | .map { response -> RxMsgpackRpc.Value in 57 | guard response.error.isNil else { throw RxNeovimApi.Error(response.error) } 58 | 59 | return response.result 60 | } 61 | } 62 | 63 | public func sendResponse(_ response: RxMsgpackRpc.Response) -> Completable { 64 | self.msgpackRpc.response(msgid: response.msgid, error: response.error, result: response.result) 65 | } 66 | 67 | public init() {} 68 | 69 | private let msgpackRpc = RxMsgpackRpc(queueQos: .userInteractive) 70 | } 71 | -------------------------------------------------------------------------------- /RxPack/Sources/RxPack/RxSwiftCommons.swift: -------------------------------------------------------------------------------- 1 | /// Tae Won Ha - http://taewon.de - @hataewon 2 | /// See LICENSE 3 | 4 | import Foundation 5 | import os 6 | import RxSwift 7 | 8 | public extension PrimitiveSequence where Element == Never, Trait == CompletableTrait { 9 | func andThen(using body: () -> Completable) -> Completable { self.andThen(body()) } 10 | 11 | func wait( 12 | timeout: TimeInterval = 5, 13 | onCompleted: (() -> Void)? = nil, 14 | onError: ((Swift.Error) -> Void)? = nil 15 | ) throws { 16 | var trigger = false 17 | var err: Swift.Error? 18 | 19 | let condition = NSCondition() 20 | 21 | condition.lock() 22 | defer { condition.unlock() } 23 | 24 | let disposable = self.subscribe(onCompleted: { 25 | onCompleted?() 26 | 27 | condition.lock() 28 | defer { condition.unlock() } 29 | trigger = true 30 | condition.broadcast() 31 | }, onError: { error in 32 | onError?(error) 33 | err = error 34 | 35 | condition.lock() 36 | defer { condition.unlock() } 37 | trigger = true 38 | condition.broadcast() 39 | }) 40 | 41 | while !trigger { 42 | condition.wait(until: Date(timeIntervalSinceNow: timeout)) 43 | trigger = true 44 | } 45 | 46 | disposable.dispose() 47 | 48 | if let e = err { throw e } 49 | } 50 | } 51 | 52 | public extension PrimitiveSequence where Trait == SingleTrait { 53 | static func fromSinglesToSingleOfArray(_ singles: [Single]) -> Single<[Element]> { 54 | Observable 55 | .merge(singles.map { $0.asObservable() }) 56 | .toArray() 57 | } 58 | 59 | func syncValue(timeout: TimeInterval = 5) -> Element? { 60 | var trigger = false 61 | var value: Element? 62 | 63 | let condition = NSCondition() 64 | 65 | condition.lock() 66 | defer { condition.unlock() } 67 | 68 | let disposable = self.subscribe(onSuccess: { result in 69 | value = result 70 | 71 | condition.lock() 72 | defer { condition.unlock() } 73 | trigger = true 74 | condition.broadcast() 75 | }, onFailure: { _ in 76 | condition.lock() 77 | defer { condition.unlock() } 78 | trigger = true 79 | condition.broadcast() 80 | }) 81 | 82 | while !trigger { 83 | condition.wait(until: Date(timeIntervalSinceNow: timeout)) 84 | trigger = true 85 | } 86 | 87 | disposable.dispose() 88 | 89 | return value 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /RxPack/bin/generate_sources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -Eeuo pipefail 3 | 4 | readonly clean=${clean:?"true or false"} 5 | 6 | build_nvimserver_bin() { 7 | ./bin/neovim/bin/build_neovim_for_dev.sh 8 | } 9 | 10 | main() { 11 | pushd "$(dirname "${BASH_SOURCE[0]}")/../.." >/dev/null 12 | 13 | echo "Make sure you have the correct ref checked out; develop or update-neovim." 14 | build_nvimserver_bin 15 | 16 | pushd RxPack >/dev/null 17 | NVIM_PATH="../Neovim/build/bin/nvim" ./bin/generate_api_methods.py 18 | swiftformat ./Sources/RxNeovim/RxNeovimApi.generated.swift 19 | popd >/dev/null 20 | 21 | popd >/dev/null 22 | } 23 | 24 | main 25 | -------------------------------------------------------------------------------- /RxPack/bin/requirements.txt: -------------------------------------------------------------------------------- 1 | msgpack 2 | -------------------------------------------------------------------------------- /Tabs/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /Tabs/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tabs/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Tabs", 7 | platforms: [.macOS(.v12)], 8 | products: [ 9 | .library(name: "Tabs", targets: ["Tabs"]), 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/qvacua/material-icons", from: "0.1.0"), 13 | .package(url: "https://github.com/PureLayout/PureLayout", from: "3.1.9"), 14 | ], 15 | targets: [ 16 | .target( 17 | name: "Tabs", 18 | dependencies: [ 19 | "PureLayout", 20 | .product(name: "MaterialIcons", package: "material-icons"), 21 | ] 22 | ), 23 | .testTarget(name: "TabsTests", dependencies: ["Tabs"]), 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /Tabs/README.md: -------------------------------------------------------------------------------- 1 | # Tabs 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /Tabs/Sources/Tabs/HorizontalOnlyScrollView.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | final class HorizontalOnlyScrollView: NSScrollView { 9 | // Needed to be able to override scrollWheel(with:) 10 | // https://stackoverflow.com/a/31201614 11 | override static var isCompatibleWithResponsiveScrolling: Bool { true } 12 | 13 | override init(frame frameRect: NSRect) { 14 | super.init(frame: frameRect) 15 | 16 | self.hasVerticalScroller = false 17 | self.verticalScrollElasticity = .none 18 | } 19 | 20 | override public func scrollWheel(with event: NSEvent) { 21 | guard let cgEvent = event.cgEvent?.copy() else { 22 | super.scrollWheel(with: event) 23 | return 24 | } 25 | 26 | if event.scrollingDeltaX != 0 { 27 | cgEvent.setDoubleValueField(.scrollWheelEventDeltaAxis1, value: 0) 28 | } else { 29 | cgEvent.setDoubleValueField(.scrollWheelEventDeltaAxis2, value: Double(event.scrollingDeltaY)) 30 | } 31 | 32 | guard let eventToForward = NSEvent(cgEvent: cgEvent) else { 33 | super.scrollWheel(with: event) 34 | return 35 | } 36 | 37 | super.scrollWheel(with: eventToForward) 38 | } 39 | 40 | @available(*, unavailable) 41 | required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } 42 | } 43 | -------------------------------------------------------------------------------- /Tabs/Sources/Tabs/Theme.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | import MaterialIcons 8 | 9 | public struct Theme { 10 | public static let `default` = Self() 11 | 12 | public var separatorColor = NSColor.gridColor 13 | public var backgroundColor = NSColor.textBackgroundColor 14 | public var foregroundColor = NSColor.textColor { 15 | didSet { 16 | self.closeButtonImage = Icon.close.asImage( 17 | dimension: self.iconDimension.width, 18 | color: self.foregroundColor 19 | ) 20 | } 21 | } 22 | 23 | public var selectedBackgroundColor = NSColor.selectedTextBackgroundColor 24 | public var selectedForegroundColor = NSColor.selectedTextColor { 25 | didSet { 26 | self.selectedCloseButtonImage = Icon.close.asImage( 27 | dimension: self.iconDimension.width, 28 | color: self.selectedForegroundColor 29 | ) 30 | } 31 | } 32 | public var tabSelectedIndicatorColor = NSColor.selectedTextColor 33 | 34 | public var tabBarBackgroundColor = NSColor.windowBackgroundColor 35 | public var tabBarForegroundColor = NSColor.textColor 36 | 37 | public var titleFont = NSFont.systemFont(ofSize: 11) 38 | public var selectedTitleFont = NSFont.boldSystemFont(ofSize: 11) 39 | 40 | public var tabHeight = CGFloat(28) 41 | 42 | public var tabMaxWidth = CGFloat(250) 43 | public var separatorThickness = CGFloat(1) 44 | public var tabHorizontalPadding = CGFloat(4) 45 | public var tabSelectionIndicatorThickness = CGFloat(3) 46 | public var iconDimension = CGSize(width: 16, height: 16) 47 | 48 | public var tabMinWidth: CGFloat { 49 | 3 * self.tabHorizontalPadding + self.iconDimension.width + 32 50 | } 51 | 52 | public var tabBarHeight: CGFloat { self.tabHeight } 53 | public var tabSpacing = CGFloat(-1) 54 | 55 | public var closeButtonImage: NSImage 56 | public var selectedCloseButtonImage: NSImage 57 | 58 | public init() { 59 | self.closeButtonImage = Icon.close.asImage( 60 | dimension: self.iconDimension.width, 61 | color: self.foregroundColor 62 | ) 63 | self.selectedCloseButtonImage = Icon.close.asImage( 64 | dimension: self.iconDimension.width, 65 | color: self.foregroundColor 66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Tabs/Support/TabsSupport/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TabsSupport 4 | // 5 | // Created by Tae Won Ha on 22.11.20. 6 | // 7 | 8 | import Cocoa 9 | import PureLayout 10 | import Tabs 11 | 12 | @main 13 | class AppDelegate: NSObject, NSApplicationDelegate { 14 | @IBOutlet var window: NSWindow! 15 | 16 | override init() { 17 | self.tabBar = TabBar(withTheme: .default) 18 | super.init() 19 | } 20 | 21 | func applicationDidFinishLaunching(_: Notification) { 22 | let contentView = self.window.contentView! 23 | contentView.addSubview(self.tabBar) 24 | 25 | let tb = self.tabBar 26 | tb.autoPinEdge(toSuperviewEdge: .top) 27 | tb.autoPinEdge(toSuperviewEdge: .left) 28 | tb.autoPinEdge(toSuperviewEdge: .right) 29 | tb.autoSetDimension(.height, toSize: Theme().tabBarHeight) 30 | tb.selectHandler = { [weak self] _, selectedEntry, _ in 31 | self?.tabEntries.enumerated().forEach { index, entry in 32 | self?.tabEntries[index].isSelected = (entry == selectedEntry) 33 | } 34 | DispatchQueue.main.async { 35 | Swift.print("select: \(self!.tabEntries)") 36 | self?.tabBar.update(tabRepresentatives: self?.tabEntries ?? []) 37 | } 38 | } 39 | tb.reorderHandler = { [weak self] index, reorderedEntry, entries in 40 | self?.tabEntries = entries 41 | self?.tabEntries.enumerated().forEach { index, entry in 42 | self?.tabEntries[index].isSelected = (entry == reorderedEntry) 43 | } 44 | DispatchQueue.main.async { 45 | Swift.print("reorder: \(entries)") 46 | self?.tabBar.update(tabRepresentatives: self?.tabEntries ?? []) 47 | } 48 | } 49 | 50 | self.tabEntries = [ 51 | DummyTabEntry(title: "Test 1"), 52 | DummyTabEntry(title: "Test 2"), 53 | DummyTabEntry(title: "Test 3"), 54 | DummyTabEntry(title: "Very long long long title, and some more text!"), 55 | ] 56 | self.tabEntries[0].isSelected = true 57 | self.tabBar.update(tabRepresentatives: self.tabEntries) 58 | } 59 | 60 | func applicationWillTerminate(_: Notification) { 61 | // Insert code here to tear down your application 62 | } 63 | 64 | private let tabBar: TabBar 65 | private var tabEntries = [DummyTabEntry]() 66 | } 67 | 68 | struct DummyTabEntry: Hashable, TabRepresentative { 69 | var title: String 70 | var isSelected = false 71 | } 72 | -------------------------------------------------------------------------------- /Tabs/Support/TabsSupport/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tabs/Support/TabsSupport/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tabs/Support/TabsSupport/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Tabs/Support/TabsSupport/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainNibFile 26 | MainMenu 27 | NSPrincipalClass 28 | NSApplication 29 | 30 | 31 | -------------------------------------------------------------------------------- /Tabs/Tests/TabsTests/TabsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Tabs 2 | import XCTest 3 | 4 | final class TabsTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(Tabs().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /VimR.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 21 | 22 | 24 | 25 | 27 | 28 | 30 | 31 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /VimR.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VimR/Dev.xcconfig: -------------------------------------------------------------------------------- 1 | VIMR_DISPLAY_NAME = VimR-dev 2 | VIMR_BUNDLE_IDENTIFIER = com.qvacua.VimR.dev 3 | -------------------------------------------------------------------------------- /VimR/Release.xcconfig: -------------------------------------------------------------------------------- 1 | VIMR_DISPLAY_NAME = VimR 2 | VIMR_BUNDLE_IDENTIFIER = com.qvacua.VimR 3 | -------------------------------------------------------------------------------- /VimR/VimR.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VimR/VimR.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VimR/VimR/AdvancedPrefReducer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class AdvancedPrefReducer: ReducerType { 9 | typealias StateType = AppState 10 | typealias ActionType = AdvancedPref.Action 11 | 12 | func typedReduce(_ pair: ReduceTuple) -> ReduceTuple { 13 | var state = pair.state 14 | 15 | switch pair.action { 16 | case let .setUseInteractiveZsh(value): 17 | state.mainWindowTemplate.useInteractiveZsh = value 18 | 19 | case let .setUseSnapshotUpdate(value): 20 | state.useSnapshotUpdate = value 21 | 22 | case let .setNvimBinary(value): 23 | state.mainWindowTemplate.nvimBinary = value 24 | } 25 | 26 | return (state, pair.action, true) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /VimR/VimR/AppearancePrefReducer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class AppearancePrefReducer: ReducerType { 9 | typealias StateType = AppState 10 | typealias ActionType = AppearancePref.Action 11 | 12 | func typedReduce(_ pair: ReduceTuple) -> ReduceTuple { 13 | var state = pair.state 14 | var appearance = state.mainWindowTemplate.appearance 15 | 16 | switch pair.action { 17 | case let .setUsesCustomTab(value): 18 | appearance.usesCustomTab = value 19 | 20 | case let .setUsesColorscheme(value): 21 | appearance.usesTheme = value 22 | 23 | case let .setShowsFileIcon(value): 24 | appearance.showsFileIcon = value 25 | 26 | case let .setUsesLigatures(value): 27 | appearance.usesLigatures = value 28 | 29 | case let .setFont(font): 30 | appearance.font = font 31 | 32 | case let .setLinespacing(linespacing): 33 | appearance.linespacing = linespacing 34 | 35 | case let .setCharacterspacing(characterspacing): 36 | appearance.characterspacing = characterspacing 37 | 38 | case let .setFontSmoothing(fontSmoothing): 39 | appearance.fontSmoothing = fontSmoothing 40 | } 41 | 42 | self.modify(state: &state, with: appearance) 43 | 44 | return (state, pair.action, true) 45 | } 46 | 47 | private func modify(state: inout AppState, with appearance: AppearanceState) { 48 | state.mainWindowTemplate.appearance = appearance 49 | state.mainWindows.keys.forEach { state.mainWindows[$0]?.appearance = appearance } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /VimR/VimR/Application.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | import Sparkle 8 | 9 | final class Application: NSApplication { 10 | override init() { 11 | setPressAndHoldSetting() 12 | super.init() 13 | } 14 | 15 | required init?(coder: NSCoder) { 16 | setPressAndHoldSetting() 17 | super.init(coder: coder) 18 | } 19 | 20 | @IBAction override func showHelp(_: Any?) { 21 | NSWorkspace.shared.open(URL(string: "https://github.com/qvacua/vimr/wiki")!) 22 | } 23 | } 24 | 25 | private func setPressAndHoldSetting() { 26 | // disable default press and hold behavior (copied from MacVim) 27 | CFPreferencesSetAppValue( 28 | "ApplePressAndHoldEnabled" as NSString, 29 | "NO" as NSString, 30 | kCFPreferencesCurrentApplication 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /VimR/VimR/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "idiom" : "mac", 23 | "size" : "32x32", 24 | "scale" : "2x" 25 | }, 26 | { 27 | "size" : "128x128", 28 | "idiom" : "mac", 29 | "filename" : "icon_128x128.png", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "size" : "128x128", 34 | "idiom" : "mac", 35 | "filename" : "icon_128x128@2x.png", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "size" : "256x256", 40 | "idiom" : "mac", 41 | "filename" : "icon_256x256.png", 42 | "scale" : "1x" 43 | }, 44 | { 45 | "size" : "256x256", 46 | "idiom" : "mac", 47 | "filename" : "icon_256x256@2x.png", 48 | "scale" : "2x" 49 | }, 50 | { 51 | "size" : "512x512", 52 | "idiom" : "mac", 53 | "filename" : "icon_512x512.png", 54 | "scale" : "1x" 55 | }, 56 | { 57 | "idiom" : "mac", 58 | "size" : "512x512", 59 | "scale" : "2x" 60 | } 61 | ], 62 | "info" : { 63 | "version" : 1, 64 | "author" : "xcode" 65 | } 66 | } -------------------------------------------------------------------------------- /VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /VimR/VimR/Base.lproj/MainWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /VimR/VimR/Base.lproj/OpenQuicklyWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /VimR/VimR/Base.lproj/PrefWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /VimR/VimR/Bridge.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | #import "ScoredUrl.h" 7 | #import "FileItem+CoreDataProperties.h" 8 | -------------------------------------------------------------------------------- /VimR/VimR/BufferListReducer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class BuffersListReducer: ReducerType { 9 | typealias StateType = MainWindow.State 10 | typealias ActionType = UuidAction 11 | 12 | func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple { 13 | var state = tuple.state 14 | 15 | switch tuple.action.payload { 16 | case let .open(buffer): 17 | state.currentBufferToSet = buffer 18 | } 19 | 20 | return (state, tuple.action, true) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /VimR/VimR/CssUtils.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | final class CssUtils { 9 | static let cssOverridesTemplate: String = try! String( 10 | contentsOf: Resources.cssOverridesTemplateUrl 11 | ) 12 | 13 | static func cssOverrides(with theme: Theme) -> String { 14 | self 15 | .cssOverridesTemplate 16 | .replacingOccurrences(of: "{{ nvim-color }}", with: self.htmlColor(theme.cssColor)) 17 | .replacingOccurrences( 18 | of: "{{ nvim-background-color }}", 19 | with: self.htmlColor(theme.cssBackgroundColor) 20 | ) 21 | .replacingOccurrences(of: "{{ nvim-a }}", with: self.htmlColor(theme.cssA)) 22 | .replacingOccurrences( 23 | of: "{{ nvim-hr-background-color }}", 24 | with: self.htmlColor(theme.cssHrBorderBackgroundColor) 25 | ) 26 | .replacingOccurrences( 27 | of: "{{ nvim-hr-border-bottom-color }}", 28 | with: self.htmlColor(theme.cssHrBorderBottomColor) 29 | ) 30 | .replacingOccurrences( 31 | of: "{{ nvim-blockquote-border-left-color }}", 32 | with: self.htmlColor(theme.cssBlockquoteBorderLeftColor) 33 | ) 34 | .replacingOccurrences( 35 | of: "{{ nvim-blockquote-color }}", 36 | with: self.htmlColor(theme.cssBlockquoteColor) 37 | ) 38 | .replacingOccurrences( 39 | of: "{{ nvim-h2-border-bottom-color }}", 40 | with: self.htmlColor(theme.cssH2BorderBottomColor) 41 | ) 42 | .replacingOccurrences(of: "{{ nvim-h6-color }}", with: self.htmlColor(theme.cssH6Color)) 43 | .replacingOccurrences( 44 | of: "{{ nvim-code-background-color }}", 45 | with: self.htmlColor(theme.cssCodeBackgroundColor) 46 | ) 47 | .replacingOccurrences(of: "{{ nvim-code-color }}", with: self.htmlColor(theme.cssCodeColor)) 48 | } 49 | 50 | private static func htmlColor(_ color: NSColor) -> String { "#\(color.hex)" } 51 | } 52 | -------------------------------------------------------------------------------- /VimR/VimR/Debouncer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | import RxSwift 8 | 9 | final class Debouncer { 10 | let observable: Observable 11 | 12 | init(interval: RxTimeInterval) { 13 | self.observable = self.subject.throttle(interval, latest: true, scheduler: self.scheduler) 14 | } 15 | 16 | deinit { 17 | self.subject.onCompleted() 18 | } 19 | 20 | func call(_ element: T) { 21 | self.subject.onNext(element) 22 | } 23 | 24 | private let subject = PublishSubject() 25 | private let scheduler = SerialDispatchQueueScheduler(qos: .userInteractive) 26 | private let disposeBag = DisposeBag() 27 | } 28 | -------------------------------------------------------------------------------- /VimR/VimR/Defs.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | import WebKit 8 | 9 | enum Defs { 10 | static let loggerSubsystem = Bundle.main.bundleIdentifier! 11 | 12 | enum LoggerCategory { 13 | static let general = "general" 14 | 15 | static let ui = "ui" 16 | static let middleware = "middleware" 17 | static let service = "service" 18 | } 19 | 20 | static let webViewProcessPool = WKProcessPool() 21 | } 22 | -------------------------------------------------------------------------------- /VimR/VimR/FileBrowserReducer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class FileBrowserReducer: ReducerType { 9 | typealias StateType = MainWindow.State 10 | typealias ActionType = UuidAction 11 | 12 | func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple { 13 | var state = tuple.state 14 | 15 | switch tuple.action.payload { 16 | case let .open(url, mode): 17 | state.urlsToOpen[url] = mode 18 | state.viewToBeFocused = .neoVimView 19 | 20 | case let .setAsWorkingDirectory(url): 21 | state.cwdToSet = url 22 | 23 | case let .setShowHidden(show): 24 | state.fileBrowserShowHidden = show 25 | 26 | case .refresh: 27 | state.lastFileSystemUpdate = Marked(state.cwd) 28 | } 29 | 30 | return (state, tuple.action, true) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /VimR/VimR/FileItem+CoreDataClass.h: -------------------------------------------------------------------------------- 1 | // 2 | // FileItem+CoreDataClass.h 3 | // VimR 4 | // 5 | // Created by Tae Won Ha on 18.01.20. 6 | // Copyright © 2020 Tae Won Ha. All rights reserved. 7 | // 8 | // 9 | 10 | #import 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface FileItem : NSManagedObject 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | 21 | #import "FileItem+CoreDataProperties.h" 22 | -------------------------------------------------------------------------------- /VimR/VimR/FileItem+CoreDataClass.m: -------------------------------------------------------------------------------- 1 | // 2 | // FileItem+CoreDataClass.m 3 | // VimR 4 | // 5 | // Created by Tae Won Ha on 18.01.20. 6 | // Copyright © 2020 Tae Won Ha. All rights reserved. 7 | // 8 | // 9 | 10 | #import "FileItem+CoreDataClass.h" 11 | 12 | @implementation FileItem 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /VimR/VimR/FileMonitor.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Commons 7 | import EonilFSEvents 8 | import Foundation 9 | import os 10 | 11 | final class FileMonitor { 12 | static let fileSystemEventsLatency = 1.0 13 | 14 | private(set) var urlToMonitor = FileUtils.userHomeUrl 15 | 16 | func monitor(url: URL, eventHandler: @escaping (URL) -> Void) throws { 17 | self.stopMonitor() 18 | self.urlToMonitor = url 19 | self.monitor = try EonilFSEventStream( 20 | pathsToWatch: [self.urlToMonitor.path], 21 | sinceWhen: EonilFSEventsEventID.getCurrentEventId(), 22 | latency: FileMonitor.fileSystemEventsLatency, 23 | flags: [], 24 | handler: { [weak self] event in 25 | if event.flag == .historyDone { 26 | self?.log.info("Not firing first event (.historyDone): \(event)") 27 | return 28 | } 29 | 30 | eventHandler(URL(fileURLWithPath: event.path)) 31 | } 32 | ) 33 | self.monitor?.setDispatchQueue(self.queue) 34 | 35 | try self.monitor?.start() 36 | self.log.info("Started monitoring \(self.urlToMonitor)") 37 | } 38 | 39 | deinit { stopMonitor() } 40 | 41 | private func stopMonitor() { 42 | self.monitor?.stop() 43 | self.monitor?.invalidate() 44 | } 45 | 46 | private var monitor: EonilFSEventStream? 47 | 48 | private let log = OSLog(subsystem: Defs.loggerSubsystem, category: Defs.LoggerCategory.service) 49 | private let queue = DispatchQueue( 50 | label: String(reflecting: FileMonitor.self) + "-\(UUID())", 51 | qos: .userInitiated, 52 | target: .global(qos: .userInitiated) 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /VimR/VimR/FoundationCommons.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | import os 8 | 9 | extension URL { 10 | var direntType: Int16 { 11 | if self.isRegularFile { return Int16(DT_REG) } 12 | if self.hasDirectoryPath { return Int16(DT_DIR) } 13 | 14 | return Int16(DT_UNKNOWN) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /VimR/VimR/FuzzySearch.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /VimR/VimR/GeneralPrefReducer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class GeneralPrefReducer: ReducerType { 9 | typealias StateType = AppState 10 | typealias ActionType = GeneralPref.Action 11 | 12 | func typedReduce(_ pair: ReduceTuple) -> ReduceTuple { 13 | var state = pair.state 14 | 15 | switch pair.action { 16 | case let .setOpenOnLaunch(value): 17 | state.openNewMainWindowOnLaunch = value 18 | 19 | case let .setOpenFilesFromApplications(action): 20 | state.openFilesFromApplicationsAction = action 21 | 22 | case let .setAfterLastWindowAction(action): 23 | state.afterLastWindowAction = action 24 | 25 | case let .setActivateAsciiImInNormalModeAction(value): 26 | state.activateAsciiImInNormalMode = value 27 | 28 | case let .setOpenOnReactivation(value): 29 | state.openNewMainWindowOnReactivation = value 30 | 31 | case let .setDefaultUsesVcsIgnores(value): 32 | state.openQuickly.defaultUsesVcsIgnores = value 33 | 34 | case let .setCustomMarkdownProcessor(command): 35 | state.mainWindowTemplate.customMarkdownProcessor = command 36 | state.mainWindows.keys.forEach { state.mainWindows[$0]?.customMarkdownProcessor = command } 37 | } 38 | 39 | return (state, pair.action, true) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /VimR/VimR/HtmlPreviewMiddleware.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Commons 7 | import Foundation 8 | 9 | final class HtmlPreviewMiddleware: MiddlewareType { 10 | static func selectFirstHtmlUrl(uuid: UUID) -> URL { 11 | FileUtils.tempDir().appendingPathComponent("\(uuid)-select-first.html") 12 | } 13 | 14 | typealias StateType = MainWindow.State 15 | typealias ActionType = UuidAction 16 | 17 | init() { 18 | self.selectFirstHtmlTemplate = try! String(contentsOf: Resources.selectFirstHtmlTemplateUrl) 19 | } 20 | 21 | func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction { 22 | { tuple in 23 | let result = reduce(tuple) 24 | 25 | if tuple.state.appearance.theme.mark != self.themeToken { 26 | self.updateCssOverrides(with: tuple.state.appearance.theme.payload) 27 | self.themeToken = tuple.state.appearance.theme.mark 28 | } 29 | 30 | self.updateCssOverrides(with: tuple.state.appearance.theme.payload) 31 | self.writeSelectFirstHtml(uuid: tuple.state.uuid) 32 | 33 | return result 34 | } 35 | } 36 | 37 | private func writeSelectFirstHtml(uuid: UUID) { 38 | let url = HtmlPreviewMiddleware.selectFirstHtmlUrl(uuid: uuid) 39 | try? self.selectFirstHtml.write(to: url, atomically: true, encoding: .utf8) 40 | } 41 | 42 | private func updateCssOverrides(with theme: Theme) { 43 | self.selectFirstHtml = self 44 | .selectFirstHtmlTemplate 45 | .replacingOccurrences(of: "{{ css-overrides }}", with: CssUtils.cssOverrides(with: theme)) 46 | } 47 | 48 | private var themeToken = Token() 49 | 50 | private var selectFirstHtml = "" 51 | private let selectFirstHtmlTemplate: String 52 | } 53 | -------------------------------------------------------------------------------- /VimR/VimR/HtmlPreviewToolReducer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class HtmlPreviewReducer { 9 | static let basePath = "tools/html-preview" 10 | 11 | static func serverUrl(baseUrl: URL, uuid: UUID) -> URL { 12 | baseUrl.appendingPathComponent("\(uuid)/\(self.basePath)/index.html") 13 | } 14 | 15 | let mainWindow: MainWindowReducer 16 | let htmlPreview: HtmlPreviewToolReducer 17 | 18 | init(baseServerUrl: URL) { 19 | self.mainWindow = MainWindowReducer(baseServerUrl: baseServerUrl) 20 | self.htmlPreview = HtmlPreviewToolReducer(baseServerUrl: baseServerUrl) 21 | } 22 | 23 | class MainWindowReducer: ReducerType { 24 | typealias StateType = MainWindow.State 25 | typealias ActionType = UuidAction 26 | 27 | init(baseServerUrl: URL) { self.baseServerUrl = baseServerUrl } 28 | 29 | func typedReduce(_ pair: ReduceTuple) -> ReduceTuple { 30 | var state = pair.state 31 | 32 | switch pair.action.payload { 33 | case .setTheme: 34 | guard state.htmlPreview.htmlFile == nil else { return pair } 35 | state.htmlPreview.server = Marked( 36 | HtmlPreviewReducer.serverUrl(baseUrl: self.baseServerUrl, uuid: state.uuid) 37 | ) 38 | 39 | default: 40 | return pair 41 | } 42 | 43 | return (state, pair.action, true) 44 | } 45 | 46 | private let baseServerUrl: URL 47 | } 48 | 49 | class HtmlPreviewToolReducer: ReducerType { 50 | typealias StateType = MainWindow.State 51 | typealias ActionType = UuidAction 52 | 53 | init(baseServerUrl: URL) { self.baseServerUrl = baseServerUrl } 54 | 55 | func typedReduce(_ pair: ReduceTuple) -> ReduceTuple { 56 | var state = pair.state 57 | switch pair.action.payload { 58 | case let .selectHtmlFile(url): 59 | state.htmlPreview.htmlFile = url 60 | state.htmlPreview.server = Marked( 61 | HtmlPreviewReducer.serverUrl(baseUrl: self.baseServerUrl, uuid: state.uuid) 62 | ) 63 | } 64 | 65 | return (state, pair.action, true) 66 | } 67 | 68 | private let baseServerUrl: URL 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /VimR/VimR/KeysPrefReducer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class KeysPrefReducer: ReducerType { 9 | typealias StateType = AppState 10 | typealias ActionType = KeysPref.Action 11 | 12 | func typedReduce(_ pair: ReduceTuple) -> ReduceTuple { 13 | var state = pair.state 14 | 15 | switch pair.action { 16 | case let .isLeftOptionMeta(value): 17 | state.mainWindowTemplate.isLeftOptionMeta = value 18 | state.mainWindows.keys.forEach { state.mainWindows[$0]?.isLeftOptionMeta = value } 19 | 20 | case let .isRightOptionMeta(value): 21 | state.mainWindowTemplate.isRightOptionMeta = value 22 | state.mainWindows.keys.forEach { state.mainWindows[$0]?.isRightOptionMeta = value } 23 | } 24 | 25 | return (state, pair.action, true) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /VimR/VimR/MainWindow+Types.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | import NvimView 8 | import Workspace 9 | 10 | extension MainWindow { 11 | enum Action { 12 | case cd(to: URL) 13 | case setBufferList([NvimView.Buffer]) 14 | 15 | case newCurrentBuffer(NvimView.Buffer) 16 | case bufferWritten(NvimView.Buffer) 17 | case setDirtyStatus(Bool) 18 | 19 | case becomeKey(isFullScreen: Bool) 20 | case frameChanged(to: CGRect) 21 | 22 | case scroll(to: Marked) 23 | case setCursor(to: Marked) 24 | 25 | case focus(FocusableView) 26 | 27 | case openQuickly 28 | 29 | case toggleAllTools(Bool) 30 | case toggleToolButtons(Bool) 31 | case setState(for: Tools, with: WorkspaceTool) 32 | case setToolsState([(Tools, WorkspaceTool)]) 33 | 34 | case makeSessionTemporary 35 | 36 | case setTheme(Theme) 37 | 38 | case close 39 | 40 | // RPC actions 41 | case setFont(NSFont) 42 | case setLinespacing(CGFloat) 43 | case setCharacterspacing(CGFloat) 44 | } 45 | 46 | enum FocusableView { 47 | case neoVimView 48 | case fileBrowser 49 | case bufferList 50 | case markdownPreview 51 | case htmlPreview 52 | } 53 | 54 | enum Tools: String, Codable { 55 | static let all = Set( 56 | [ 57 | Tools.fileBrowser, 58 | Tools.buffersList, 59 | Tools.preview, 60 | Tools.htmlPreview, 61 | ] 62 | ) 63 | 64 | case fileBrowser = "com.qvacua.vimr.tools.file-browser" 65 | case buffersList = "com.qvacua.vimr.tools.opened-files-list" 66 | case preview = "com.qvacua.vimr.tools.preview" 67 | case htmlPreview = "com.qvacua.vimr.tools.html-preview" 68 | } 69 | 70 | enum OpenMode { 71 | case `default` 72 | case currentTab 73 | case newTab 74 | case horizontalSplit 75 | case verticalSplit 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /VimR/VimR/MainWindowReducer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class MainWindowReducer: ReducerType { 9 | typealias StateType = MainWindow.State 10 | typealias ActionType = UuidAction 11 | 12 | func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple { 13 | var state = tuple.state 14 | 15 | switch tuple.action.payload { 16 | case let .frameChanged(to: frame): 17 | state.frame = frame 18 | 19 | case let .cd(to: cwd): 20 | if state.cwd != cwd { 21 | state.cwd = cwd 22 | } 23 | 24 | case let .setBufferList(buffers): 25 | state.buffers = buffers 26 | 27 | case let .newCurrentBuffer(buffer): 28 | state.currentBuffer = buffer 29 | 30 | case let .setDirtyStatus(status): 31 | // When I gt or w around, we change tab somehow... Dunno why... 32 | if status == tuple.state.isDirty { 33 | return tuple 34 | } 35 | 36 | state.isDirty = status 37 | 38 | case let .focus(view): 39 | state.viewToBeFocused = view 40 | 41 | case let .setState(for: tool, with: workspaceTool): 42 | state.tools[tool] = WorkspaceToolState( 43 | location: workspaceTool.location, 44 | dimension: workspaceTool.dimension, 45 | open: workspaceTool.isSelected 46 | ) 47 | if workspaceTool.isSelected { 48 | state.tools 49 | .filter { $0 != tool && $1.location == workspaceTool.location } 50 | .forEach { state.tools[$0.0]?.open = false } 51 | } 52 | 53 | case let .setToolsState(tools): 54 | state.orderedTools = [] 55 | for toolPair in tools { 56 | let toolId = toolPair.0 57 | let tool = toolPair.1 58 | 59 | state.tools[toolId] = WorkspaceToolState( 60 | location: tool.location, 61 | dimension: tool.dimension, 62 | open: tool.isSelected 63 | ) 64 | 65 | if tool.isSelected { 66 | state.tools 67 | .filter { $0 != toolId && $1.location == tool.location } 68 | .forEach { state.tools[$0.0]?.open = false } 69 | } 70 | 71 | state.orderedTools.append(toolId) 72 | } 73 | 74 | case let .toggleAllTools(value): 75 | state.isAllToolsVisible = value 76 | 77 | case let .toggleToolButtons(value): 78 | state.isToolButtonsVisible = value 79 | 80 | case let .setTheme(theme): 81 | state.appearance.theme = Marked(theme) 82 | 83 | case .makeSessionTemporary: 84 | state.isTemporarySession = true 85 | 86 | default: 87 | return tuple 88 | } 89 | 90 | return (state, tuple.action, true) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /VimR/VimR/MarkdownToolReducer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class MarkdownToolReducer: ReducerType { 9 | typealias StateType = MainWindow.State 10 | typealias ActionType = UuidAction 11 | 12 | init(baseServerUrl: URL) { 13 | self.baseServerUrl = baseServerUrl 14 | } 15 | 16 | func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple { 17 | var state = tuple.state 18 | 19 | switch tuple.action.payload { 20 | case let .setAutomaticReverseSearch(to: value): 21 | state.previewTool.isReverseSearchAutomatically = value 22 | 23 | case let .setAutomaticForwardSearch(to: value): 24 | state.previewTool.isForwardSearchAutomatically = value 25 | 26 | case let .setRefreshOnWrite(to: value): 27 | state.previewTool.isRefreshOnWrite = value 28 | 29 | default: 30 | return tuple 31 | } 32 | 33 | return (state, tuple.action, true) 34 | } 35 | 36 | private let baseServerUrl: URL 37 | } 38 | -------------------------------------------------------------------------------- /VimR/VimR/OpenQuicklyFileViewRow.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | final class OpenQuicklyFileViewRow: NSTableRowView { 9 | override func drawSelection(in dirtyRect: NSRect) { 10 | if self.isSelected { 11 | NSColor.selectedControlColor.set() 12 | } else { 13 | NSColor.clear.set() 14 | } 15 | 16 | self.rectsBeingDrawn().forEach { NSIntersectionRect($0, dirtyRect).fill(using: .sourceOver) } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /VimR/VimR/OpenQuicklyReducer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | import RxSwift 8 | 9 | final class OpenQuicklyReducer: ReducerType { 10 | typealias StateType = AppState 11 | typealias ActionType = OpenQuicklyWindow.Action 12 | 13 | let mainWindow = MainWindowReducer() 14 | 15 | func typedReduce(_ pair: ReduceTuple) -> ReduceTuple { 16 | var appState = pair.state 17 | 18 | switch pair.action { 19 | case let .setUsesVcsIgnores(usesVcsIgnores): 20 | guard let uuid = appState.currentMainWindowUuid else { return pair } 21 | appState.mainWindows[uuid]?.usesVcsIgnores = usesVcsIgnores 22 | 23 | case let .open(url): 24 | guard let uuid = appState.currentMainWindowUuid else { return pair } 25 | appState.mainWindows[uuid]?.urlsToOpen[url] = .default 26 | appState.openQuickly.open = false 27 | 28 | case .close: 29 | appState.openQuickly.open = false 30 | } 31 | 32 | return (appState, pair.action, true) 33 | } 34 | 35 | class MainWindowReducer: ReducerType { 36 | typealias StateType = AppState 37 | typealias ActionType = UuidAction 38 | 39 | func typedReduce(_ pair: ReduceTuple) -> ReduceTuple { 40 | switch pair.action.payload { 41 | case .openQuickly: 42 | var appState = pair.state 43 | 44 | guard let uuid = appState.currentMainWindowUuid, 45 | appState.mainWindows[uuid]?.cwd != nil else { return pair } 46 | 47 | appState.openQuickly.open = true 48 | 49 | return (appState, pair.action, true) 50 | 51 | default: 52 | return pair 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /VimR/VimR/PrefPane.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | class PrefPane: NSView { 9 | // Return true to place this to the upper left corner when the scroll view is bigger than this 10 | // view. 11 | override var isFlipped: Bool { 12 | true 13 | } 14 | 15 | var displayName: String { 16 | preconditionFailure("Please override") 17 | } 18 | 19 | var pinToContainer: Bool { 20 | false 21 | } 22 | 23 | func paneWillAppear() { 24 | // noop, override 25 | } 26 | 27 | func windowWillClose() { 28 | // noop, override 29 | } 30 | } 31 | 32 | // MARK: - Control Utils 33 | 34 | extension PrefPane { 35 | func paneTitleTextField(title: String) -> NSTextField { 36 | let field = NSTextField.defaultTitleTextField() 37 | field.font = NSFont.boldSystemFont(ofSize: 16) 38 | field.alignment = .left 39 | field.stringValue = title 40 | return field 41 | } 42 | 43 | func titleTextField(title: String) -> NSTextField { 44 | let field = NSTextField.defaultTitleTextField() 45 | field.alignment = .right 46 | field.stringValue = title 47 | return field 48 | } 49 | 50 | func infoTextField(markdown: String) -> NSTextField { 51 | let field = NSTextField(forAutoLayout: ()) 52 | field.backgroundColor = NSColor.clear 53 | field.isEditable = false 54 | field.isBordered = false 55 | field.usesSingleLineMode = false 56 | 57 | // both are needed, otherwise hyperlink won't accept mousedown 58 | field.isSelectable = true 59 | field.allowsEditingTextAttributes = true 60 | 61 | field.attributedStringValue = NSAttributedString.infoLabel(markdown: markdown) 62 | 63 | return field 64 | } 65 | 66 | func configureCheckbox(button: NSButton, title: String, action: Selector) { 67 | button.title = title 68 | button.setButtonType(.switch) 69 | button.target = self 70 | button.action = action 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /VimR/VimR/PrefWindowReducer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class PrefWindowReducer: ReducerType { 9 | typealias StateType = AppState 10 | typealias ActionType = PrefWindow.Action 11 | 12 | func typedReduce(_ pair: ReduceTuple) -> ReduceTuple { 13 | var state = pair.state 14 | 15 | switch pair.action { 16 | case .close: 17 | state.preferencesOpen = Marked(false) 18 | } 19 | 20 | return (state, pair.action, true) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /VimR/VimR/Resources.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | enum Resources { 9 | static let resourceUrl = Bundle.main.resourceURL! 10 | static let previewUrl = resourceUrl.appendingPathComponent("preview") 11 | 12 | static let cssOverridesTemplateUrl = resourceUrl 13 | .appendingPathComponent("markdown/color-overrides.css") 14 | static let cssUrl = resourceUrl.appendingPathComponent("markdown/github-markdown.css") 15 | static let markdownTemplateUrl = resourceUrl.appendingPathComponent("markdown/template.html") 16 | 17 | static let baseCssUrl = previewUrl.appendingPathComponent("base.css") 18 | static let emptyHtmlTemplateUrl = previewUrl.appendingPathComponent("empty.html") 19 | static let errorHtmlTemplateUrl = previewUrl.appendingPathComponent("error.html") 20 | static let saveFirstHtmlTemplateUrl = previewUrl.appendingPathComponent("save-first.html") 21 | static let selectFirstHtmlTemplateUrl = previewUrl.appendingPathComponent("select-first.html") 22 | } 23 | -------------------------------------------------------------------------------- /VimR/VimR/RpcAppearanceEpic.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class RpcAppearanceEpic: EpicType { 9 | typealias StateType = AppState 10 | typealias ActionType = UuidAction 11 | typealias EmitActionType = AppearancePref.Action 12 | 13 | required init(emitter: ActionEmitter) { 14 | self.emit = emitter.typedEmit() 15 | } 16 | 17 | func typedApply( 18 | _ reduce: @escaping TypedActionReduceFunction 19 | ) -> TypedActionReduceFunction { 20 | { tuple in 21 | let result = reduce(tuple) 22 | 23 | switch tuple.action.payload { 24 | case let .setFont(font): 25 | self.emit(.setFont(font)) 26 | 27 | case let .setLinespacing(linespacing): 28 | self.emit(.setLinespacing(linespacing)) 29 | 30 | case let .setCharacterspacing(characterspacing): 31 | self.emit(.setCharacterspacing(characterspacing)) 32 | 33 | default: 34 | break 35 | } 36 | 37 | return result 38 | } 39 | } 40 | 41 | private let emit: (EmitActionType) -> Void 42 | } 43 | -------------------------------------------------------------------------------- /VimR/VimR/RpcEvents.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | enum RpcEvent: String, CaseIterable { 9 | static let prefix = "com.qvacua.vimr.rpc-events" 10 | 11 | case makeSessionTemporary = "com.qvacua.vimr.rpc-events.make-session-temporary" 12 | case maximizeWindow = "com.qvacua.vimr.rpc-events.maximize-window" 13 | case toggleTools = "com.qvacua.vimr.rpc-events.toggle-tools" 14 | case toggleToolButtons = "com.qvacua.vimr.rpc-events.toggle-tool-buttons" 15 | case toggleFullScreen = "com.qvacua.vimr.rpc-events.toggle-fullscreen" 16 | 17 | case setFont = "com.qvacua.vimr.rpc-events.set-font" 18 | case setLinespacing = "com.qvacua.vimr.rpc-events.set-linespacing" 19 | case setCharacterspacing = "com.qvacua.vimr.rpc-events.set-characterspacing" 20 | 21 | case revealCurrentBufferInFileBrowser = 22 | "com.qvacua.vimr.rpc-events.reveal-current-buffer-in-file-browser" 23 | case refreshFileBrowser = "com.qvacua.vimr.rpc-events.refresh-file-browser" 24 | } 25 | -------------------------------------------------------------------------------- /VimR/VimR/RxCommons.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | import RxSwift 8 | 9 | extension Observable { 10 | func completableSubject() -> CompletableSubject { CompletableSubject(source: self) } 11 | } 12 | 13 | final class CompletableSubject { 14 | func asObservable() -> Observable { self.subject.asObservable() } 15 | 16 | init(source: Observable) { 17 | let subject = PublishSubject() 18 | self.subscription = source.subscribe(onNext: { element in subject.onNext(element) }) 19 | self.subject = subject 20 | } 21 | 22 | func onCompleted() { 23 | self.subject.onCompleted() 24 | self.subscription.dispose() 25 | } 26 | 27 | private let subject: PublishSubject 28 | private let subscription: Disposable 29 | } 30 | -------------------------------------------------------------------------------- /VimR/VimR/ScoredUrl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | #import 7 | 8 | 9 | @interface ScoredUrl : NSObject 10 | 11 | @property (readonly, nonnull) NSURL *url; 12 | @property (readonly) double score; 13 | 14 | - (instancetype _Nonnull)initWithUrl:(NSURL * _Nonnull)url score:(double)score; 15 | - (NSString * _Nonnull)description; 16 | - (BOOL)isEqual:(id _Nullable)other; 17 | - (BOOL)isEqualToUrl:(ScoredUrl * _Nullable)url; 18 | - (NSUInteger)hash; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /VimR/VimR/ScoredUrl.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | #import "ScoredUrl.h" 7 | 8 | 9 | @implementation ScoredUrl { 10 | } 11 | 12 | - (BOOL)isEqual:(id)other { 13 | if (other == self) 14 | return YES; 15 | if (!other || ![[other class] isEqual:[self class]]) 16 | return NO; 17 | 18 | return [self isEqualToUrl:other]; 19 | } 20 | 21 | - (BOOL)isEqualToUrl:(ScoredUrl *)url { 22 | if (self == url) 23 | return YES; 24 | if (url == nil) 25 | return NO; 26 | if (self.url != url.url && ![self.url isEqual:url.url]) 27 | return NO; 28 | if (self.score != url.score) 29 | return NO; 30 | return YES; 31 | } 32 | 33 | - (NSUInteger)hash { 34 | NSUInteger hash = self.url.hash; 35 | hash = hash * 31u + @(self.score).hash; 36 | return hash; 37 | } 38 | 39 | - (NSString *)description { 40 | NSMutableString *description 41 | = [NSMutableString stringWithFormat:@"<%@: ", NSStringFromClass([self class])]; 42 | [description appendFormat:@"self.url=%@", self.url.path]; 43 | [description appendFormat:@", self.score=%f", self.score]; 44 | [description appendString:@">"]; 45 | return description; 46 | } 47 | 48 | - (instancetype)initWithUrl:(NSURL *)url score:(double)score { 49 | self = [super init]; 50 | if (!self) { return nil; } 51 | 52 | _url = url; 53 | _score = score; 54 | 55 | return self; 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /VimR/VimR/ShortcutItem.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | 8 | final class ShortcutItem: NSObject, Comparable { 9 | static func < (lhs: ShortcutItem, rhs: ShortcutItem) -> Bool { lhs.title < rhs.title } 10 | 11 | @objc dynamic var title: String 12 | @objc dynamic var isLeaf: Bool 13 | 14 | @objc dynamic var childrenCount: Int { self.children?.count ?? -1 } 15 | 16 | var identifier: String? { self.item?.identifier?.rawValue } 17 | 18 | var isContainer: Bool { !self.isLeaf } 19 | 20 | override var description: String { 21 | "" 26 | } 27 | 28 | let item: NSMenuItem? 29 | @objc dynamic var children: [ShortcutItem]? 30 | 31 | init(title: String, isLeaf: Bool, item: NSMenuItem?) { 32 | self.title = title 33 | self.isLeaf = isLeaf 34 | self.item = item 35 | self.children = isLeaf ? nil : [] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /VimR/VimR/ShortcutService.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | import ShortcutRecorder 8 | 9 | final class ShortcutService { 10 | func update(shortcuts: [Shortcut]) { 11 | self.shortcuts = shortcuts 12 | } 13 | 14 | func isMenuItemShortcut(_ event: NSEvent) -> Bool { 15 | if let shortcut = Shortcut(event: event), self.shortcuts.contains(shortcut) { return true } 16 | 17 | return false 18 | } 19 | 20 | private var shortcuts = [Shortcut]() 21 | } 22 | -------------------------------------------------------------------------------- /VimR/VimR/ToolsPrefReducer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Foundation 7 | 8 | final class ToolsPrefReducer: ReducerType { 9 | typealias StateType = AppState 10 | typealias ActionType = ToolsPref.Action 11 | 12 | func typedReduce(_ pair: ReduceTuple) -> ReduceTuple { 13 | var state = pair.state 14 | 15 | switch pair.action { 16 | case let .setActiveTools(tools): 17 | state.mainWindowTemplate.activeTools = tools 18 | } 19 | 20 | return (state, pair.action, true) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /VimR/VimR/com.qvacua.VimR.vim: -------------------------------------------------------------------------------- 1 | function! s:VimRMakeSessionTemporary() abort 2 | call rpcnotify(0, 'com.qvacua.NvimView', 'make-session-temporary') 3 | endfunction 4 | command! -nargs=0 VimRMakeSessionTemporary call s:VimRMakeSessionTemporary() 5 | 6 | function! s:VimRMaximizeWindow() abort 7 | call rpcnotify(0, 'com.qvacua.NvimView', 'maximize-window') 8 | endfunction 9 | command! -nargs=0 VimRMaximizeWindow call s:VimRMaximizeWindow() 10 | 11 | " -1: hide, 0: toggle, 1: show 12 | function! s:VimRToggleTools(value) abort 13 | call rpcnotify(0, 'com.qvacua.NvimView', 'toggle-tools', a:value) 14 | endfunction 15 | command! -nargs=0 VimRHideTools call s:VimRToggleTools(-1) 16 | command! -nargs=0 VimRToggleTools call s:VimRToggleTools(0) 17 | command! -nargs=0 VimRShowTools call s:VimRToggleTools(1) 18 | 19 | " -1: hide, 0: toggle, 1: show 20 | function! s:VimRToggleToolButtons(value) abort 21 | call rpcnotify(0, 'com.qvacua.NvimView', 'toggle-tool-buttons', a:value) 22 | endfunction 23 | 24 | command! -nargs=0 VimRHideToolButtons call s:VimRToggleToolButtons(-1) 25 | command! -nargs=0 VimRToggleToolButtons call s:VimRToggleToolButtons(0) 26 | command! -nargs=0 VimRShowToolButtons call s:VimRToggleToolButtons(1) 27 | 28 | function! s:VimRRevealCurrentBufferInFileBrowser() abort 29 | if filereadable(expand('%')) 30 | call rpcnotify(0, 'com.qvacua.NvimView', 'reveal-current-buffer-in-file-browser') 31 | endif 32 | endfunction 33 | command! -nargs=0 VimRRevealCurrentBuffer call s:VimRRevealCurrentBufferInFileBrowser() 34 | 35 | function! s:VimRRefreshFileBrowser() abort 36 | call rpcnotify(0, 'com.qvacua.NvimView', 'refresh-file-browser') 37 | endfunction 38 | command! -nargs=0 VimRRefreshFileBrowser call s:VimRRefreshFileBrowser() 39 | 40 | function! s:VimRToggleFullscreen() abort 41 | call rpcnotify(0, 'com.qvacua.NvimView', 'toggle-fullscreen') 42 | endfunction 43 | command! -nargs=0 VimRToggleFullscreen call s:VimRToggleFullscreen() 44 | 45 | function! s:VimRSetFontAndSize(font, size) abort 46 | call rpcnotify(0, 'com.qvacua.NvimView', 'set-font', a:font, a:size) 47 | endfunction 48 | command! -nargs=* VimRSetFontAndSize call s:VimRSetFontAndSize() 49 | 50 | function! s:VimRSetLinespacing(linespacing) abort 51 | call rpcnotify(0, 'com.qvacua.NvimView', 'set-linespacing', a:linespacing) 52 | endfunction 53 | command! -nargs=1 VimRSetLinespacing call s:VimRSetLinespacing() 54 | 55 | function! s:VimRSetCharacterspacing(characterspacing) abort 56 | call rpcnotify(0, 'com.qvacua.NvimView', 'set-characterspacing', a:characterspacing) 57 | endfunction 58 | command! -nargs=1 VimRSetCharacterspacing call s:VimRSetCharacterspacing() 59 | -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-applescript.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-applescript.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-as.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-as.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-asp.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-asp.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-bash.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-bash.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-bib.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-bib.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-bsh.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-bsh.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-c.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-c.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-cfg.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-cfg.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-cgi.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-cgi.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-cpp.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-cpp.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-cs.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-cs.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-csfg.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-csfg.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-css.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-css.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-csv.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-csv.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-dtd.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-dtd.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-dylan.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-dylan.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-erl.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-erl.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-f.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-f.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-fscript.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-fscript.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-generic.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-generic.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-gtd.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-gtd.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-h.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-h.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-hs.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-hs.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-html.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-html.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-ics.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-ics.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-inc.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-inc.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-ini.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-ini.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-io.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-io.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-java.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-java.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-js.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-js.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-jsp.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-jsp.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-lisp.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-lisp.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-log.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-log.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-m.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-m.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-markdown.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-markdown.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-mm.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-mm.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-patch.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-patch.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-perl.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-perl.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-php.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-php.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-plist.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-plist.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-properties.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-properties.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-ps.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-ps.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-py.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-py.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-rb.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-rb.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-rst.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-rst.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-sch.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-sch.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-sql.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-sql.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-tcl.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-tcl.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-tex.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-tex.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-tsv.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-tsv.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-txt.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-txt.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-vb.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-vb.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-vba.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-vba.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-vcf.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-vcf.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-vim.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-vim.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-wiki.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-wiki.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-xml.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-xml.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-xsl.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-xsl.icns -------------------------------------------------------------------------------- /VimR/VimR/macvim-file-icons/MacVim-yaml.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/VimR/VimR/macvim-file-icons/MacVim-yaml.icns -------------------------------------------------------------------------------- /VimR/VimR/markdown/color-overrides.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --nvim-color: {{ nvim-color }}; 3 | --nvim-background-color: {{ nvim-background-color }}; 4 | --nvim-a: {{ nvim-a }}; 5 | --nvim-hr-background-color: {{ nvim-hr-background-color }}; 6 | --nvim-hr-border-bottom-color: {{ nvim-hr-border-bottom-color }}; 7 | --nvim-blockquote-border-left-color: {{ nvim-blockquote-border-left-color }}; 8 | --nvim-blockquote-color: {{ nvim-blockquote-color }}; 9 | --nvim-h2-border-bottom-color: {{ nvim-h2-border-bottom-color }}; 10 | --nvim-h6-color: {{ nvim-h6-color }}; 11 | --nvim-code-background-color: {{ nvim-code-background-color }}; 12 | --nvim-code-color: {{ nvim-code-color }}; 13 | } 14 | 15 | .markdown-body { 16 | color: var(--nvim-color); 17 | background-color: var(--nvim-background-color); 18 | } 19 | 20 | .markdown-body a { 21 | color: var(--nvim-a); 22 | } 23 | 24 | .markdown-body hr { 25 | background-color: var(--nvim-hr-background-color); 26 | border-bottom-color: var(--nvim-hr-border-bottom-color); 27 | } 28 | 29 | .markdown-body blockquote { 30 | border-left-color: var(--nvim-blockquote-border-left-color); 31 | color: var(--nvim-blockquote-foreground); 32 | } 33 | 34 | .markdown-body h2 { 35 | border-bottom-color: var(--nvim-border-bottom-color); 36 | } 37 | 38 | .markdown-body h6 { 39 | color: var(--nvim-h6-color); 40 | } 41 | 42 | .markdown-body pre { 43 | color: var(--nvim-code-color); 44 | background-color: var(--nvim-code-background-color); 45 | } 46 | -------------------------------------------------------------------------------- /VimR/VimR/preview/base.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | vertical-align: baseline; 23 | } 24 | /* HTML5 display-role reset for older browsers */ 25 | article, aside, details, figcaption, figure, 26 | footer, header, hgroup, menu, nav, section { 27 | display: block; 28 | } 29 | body { 30 | line-height: 1; 31 | } 32 | ol, ul { 33 | list-style: none; 34 | } 35 | blockquote, q { 36 | quotes: none; 37 | } 38 | blockquote:before, blockquote:after, 39 | q:before, q:after { 40 | content: none; 41 | } 42 | table { 43 | border-collapse: collapse; 44 | border-spacing: 0; 45 | } 46 | 47 | body { 48 | font-family: -apple-system, sans-serif; 49 | } 50 | 51 | #message { 52 | text-align: center; 53 | position: absolute; 54 | top: 50%; 55 | left: 50%; 56 | transform: translate(-50%, -50%); 57 | } 58 | 59 | .info-text { 60 | font-size: small; 61 | color: gray; 62 | } 63 | -------------------------------------------------------------------------------- /VimR/VimR/preview/empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | Empty 11 | 12 | 13 |
14 |

😶

15 | no preview... 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /VimR/VimR/preview/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | Error 11 | 12 | 13 |
14 |

😱

15 | There was an error... 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /VimR/VimR/preview/save-first.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | Error 11 | 12 | 13 |
14 |

😶

15 | Save first for preview 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /VimR/VimR/preview/select-first.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | Error 11 | 12 | 13 |
14 |

😶

15 | Select an HTML file for preview 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /VimR/VimRTests/IgnoreServiceTest.swift: -------------------------------------------------------------------------------- 1 | /// Tae Won Ha - http://taewon.de - @hataewon 2 | /// See LICENSE 3 | 4 | import Nimble 5 | import XCTest 6 | 7 | class IgnoreServiceTest: XCTestCase { 8 | var base: URL! 9 | var service: IgnoreService! 10 | 11 | override func setUp() { 12 | self.base = Bundle(for: type(of: self)).url( 13 | forResource: "ignore-service-test", 14 | withExtension: nil, 15 | subdirectory: "Resources" 16 | )! 17 | self.service = IgnoreService(count: 100, root: self.base) 18 | 19 | super.setUp() 20 | } 21 | 22 | func testDeepest() { 23 | let ignoreAaa = self.service.ignore(for: self.base.appendingPathComponent("a/aa/aaa"))! 24 | 25 | expect(ignoreAaa.filters.count).to(beGreaterThanOrEqualTo(4)) 26 | expect(ignoreAaa.filters[back: 0].pattern).to(equal("last-level")) 27 | expect(ignoreAaa.filters[back: 1].pattern).to(equal("level-aaa")) 28 | expect(ignoreAaa.filters[back: 2].pattern).to(equal("level-a")) 29 | expect(ignoreAaa.filters[back: 3].pattern).to(equal("root-level")) 30 | } 31 | 32 | func testWholeTree() { 33 | let ignoreBase = self.service.ignore(for: self.base)! 34 | let ignoreA = self.service.ignore(for: self.base.appendingPathComponent("a/"))! 35 | let ignoreAa = self.service.ignore(for: self.base.appendingPathComponent("a/aa/"))! 36 | let ignoreAaa = self.service.ignore(for: self.base.appendingPathComponent("a/aa/aaa"))! 37 | 38 | expect(ignoreBase.filters.count).to(beGreaterThanOrEqualTo(1)) 39 | expect(ignoreBase.filters[back: 0].pattern).to(equal("root-level")) 40 | 41 | expect(ignoreA.filters.count).to(equal(ignoreBase.filters.count + 1)) 42 | expect(ignoreA.filters[back: 0].pattern).to(equal("level-a")) 43 | expect(ignoreA.filters[back: 1].pattern).to(equal("root-level")) 44 | 45 | expect(ignoreAa).to(be(ignoreA)) 46 | 47 | expect(ignoreAaa.filters.count).to(equal(ignoreAa.filters.count + 2)) 48 | expect(ignoreAaa.filters[back: 0].pattern).to(equal("last-level")) 49 | expect(ignoreAaa.filters[back: 1].pattern).to(equal("level-aaa")) 50 | expect(ignoreAaa.filters[back: 2].pattern).to(equal("level-a")) 51 | expect(ignoreAaa.filters[back: 3].pattern).to(equal("root-level")) 52 | } 53 | } 54 | 55 | private extension BidirectionalCollection { 56 | subscript(back i: Int) -> Element { self[index(endIndex, offsetBy: -(i + 1))] } 57 | } 58 | -------------------------------------------------------------------------------- /VimR/VimRTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 0.54.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 20250531.222551 23 | 24 | 25 | -------------------------------------------------------------------------------- /VimR/VimRTests/Resources/ignore-service-test/.gitignore: -------------------------------------------------------------------------------- 1 | root-level 2 | 3 | -------------------------------------------------------------------------------- /VimR/VimRTests/Resources/ignore-service-test/a/.gitignore: -------------------------------------------------------------------------------- 1 | level-a 2 | 3 | -------------------------------------------------------------------------------- /VimR/VimRTests/Resources/ignore-service-test/a/aa/aaa/.gitignore: -------------------------------------------------------------------------------- 1 | level-aaa 2 | last-level 3 | 4 | -------------------------------------------------------------------------------- /Workspace/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /Workspace/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Workspace", 7 | platforms: [.macOS(.v12)], 8 | products: [ 9 | .library(name: "Workspace", targets: ["Workspace"]), 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/PureLayout/PureLayout", from: "3.1.9"), 13 | .package(url: "https://github.com/qvacua/material-icons", from: "0.1.0"), 14 | .package(path: "../Commons"), 15 | ], 16 | targets: [ 17 | .target( 18 | name: "Workspace", 19 | dependencies: [ 20 | "PureLayout", 21 | .product(name: "MaterialIcons", package: "material-icons"), 22 | "Commons", 23 | ] 24 | ), 25 | .testTarget( 26 | name: "WorkspaceTests", 27 | dependencies: ["Workspace"] 28 | ), 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /Workspace/README.md: -------------------------------------------------------------------------------- 1 | # Workspace 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /Workspace/Sources/Workspace/ProxyWorkspaceBar.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Tae Won Ha - http://taewon.de - @hataewon 3 | * See LICENSE 4 | */ 5 | 6 | import Cocoa 7 | import PureLayout 8 | 9 | /** 10 | This class is used to display the placeholder bar when a tool is drag & dropped to a location with no existing tools. 11 | */ 12 | final class ProxyWorkspaceBar: NSView { 13 | var theme = Workspace.Theme.default 14 | 15 | @available(*, unavailable) 16 | required init?(coder _: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | 20 | override init(frame: NSRect) { 21 | super.init(frame: frame) 22 | 23 | // Because other views also want layer, this view also must want layer. Otherwise the z-index 24 | // ordering is not set 25 | // correctly: views w/ wantsLayer = false are behind views w/ wantsLayer = true even when added later. 26 | self.wantsLayer = true 27 | self.layer?.backgroundColor = self.theme.background.cgColor 28 | } 29 | 30 | func repaint() { 31 | self.layer?.backgroundColor = self.theme.background.cgColor 32 | self.needsDisplay = true 33 | } 34 | 35 | override func draw(_: NSRect) { 36 | let path = NSBezierPath(rect: self.bounds) 37 | path.lineWidth = 4 38 | self.theme.barFocusRing.set() 39 | path.stroke() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Workspace/Tests/WorkspaceTests/WorkspaceTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Workspace 2 | import XCTest 3 | 4 | final class WorkspaceTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(Workspace().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | VimR with NeoVim 5 | https://twitter.com/vimrefined 6 | Most recent changes with links to updates for VimR. 7 | en 8 | 9 | v0.54.0-20250531.222551 10 | https://twitter.com/vimrefined 11 | 20250531.222551 12 | v0.54.0 13 | 15 |
  • Neovim 0.11.2 😀
  • 16 | 17 | ]]>
    18 | 19 | https://github.com/qvacua/vimr/releases/tag/v0.54.0-20250531.222551 20 | 21 | 2025-05-31T22:29:12.149530 22 | 12.0 23 | 26 |
    27 |
    28 |
    29 | -------------------------------------------------------------------------------- /appcast_snapshot.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | VimR with NeoVim 5 | https://twitter.com/vimrefined 6 | Most recent changes with links to updates for VimR. 7 | en 8 | 9 | v0.54.0-20250531.222551 10 | https://twitter.com/vimrefined 11 | 20250531.222551 12 | v0.54.0 13 | 15 |
  • Neovim 0.11.2 😀
  • 16 | 17 | ]]>
    18 | 19 | https://github.com/qvacua/vimr/releases/tag/v0.54.0-20250531.222551 20 | 21 | 2025-05-31T22:29:12.149530 22 | 12.0 23 | 26 |
    27 |
    28 |
    29 | -------------------------------------------------------------------------------- /bin/.python-version: -------------------------------------------------------------------------------- 1 | com.qvacua.VimR.bin 2 | -------------------------------------------------------------------------------- /bin/README.md: -------------------------------------------------------------------------------- 1 | ## How to use 2 | 3 | * `cd` into `${PROJECT_ROOT}/bin`, this directory. 4 | * Install pyenv and pyenv-virtuelenv. 5 | * Install Python 3.9.7 using pyenv. 6 | * Create a virtualenv with the name `com.qvacua.VimR.bin`. 7 | * Ensure that you're running the Python in the virtualenv by 8 | ```bash 9 | pyenv which python 10 | /${HOME}/.pyenv/versions/com.qvacua.VimR.bin/bin/python 11 | ``` 12 | * Install the requirements 13 | ```bash 14 | pip install -r requirements.txt 15 | ``` 16 | -------------------------------------------------------------------------------- /bin/build_jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -Eeuo pipefail 3 | 4 | readonly branch=${branch:?"which branch to use"} 5 | readonly create_gh_release=${create_gh_release:?"create Github release?"} 6 | readonly upload=${upload:?"upload artifact to github release?"} 7 | readonly update_appcast=${update_appcast:?"update and push appcast?"} 8 | 9 | # release.spec.sh will declare these two variables 10 | release_notes=${release_notes:?"release notes"} 11 | is_snapshot=${is_snapshot:?"is snapshot?"} 12 | 13 | check_parameters() { 14 | if [[ "${is_snapshot}" == false && -z "${marketing_version}" ]]; then 15 | echo "### No marketing_version for a release version! Exiting" 16 | exit 1 17 | fi 18 | 19 | if [[ "${create_gh_release}" == true && -z "${release_notes}" ]]; then 20 | echo "### No release notes when creating github release! Exiting" 21 | exit 1 22 | fi 23 | } 24 | 25 | main() { 26 | echo "### Releasing VimR started" 27 | 28 | pushd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 29 | 30 | check_parameters 31 | 32 | git submodule update --init 33 | git checkout "${branch}" 34 | git pull 35 | 36 | ./bin/set_new_versions.sh 37 | 38 | echo "### Store release notes" 39 | echo "${release_notes}" > release-notes.temp.md 40 | 41 | # commit and push the tag 42 | # get the marketing version to be used as tag 43 | source release.spec.sh 44 | 45 | if [[ "${is_snapshot}" == true ]]; then 46 | tag_name="snapshot/${bundle_version}" 47 | else 48 | tag_name="${marketing_version}-${bundle_version}" 49 | fi 50 | echo "### Using ${tag_name} as tag name" 51 | git commit -am "Bump version to ${tag_name}" 52 | git tag -a "${tag_name}" -m "${tag_name}" 53 | git push 54 | git push origin "${tag_name}" 55 | 56 | echo "### Build VimR" 57 | 58 | release_spec_file=release.spec.sh ./bin/build_release.sh 59 | 60 | if [[ "${create_gh_release}" == false ]]; then 61 | echo "### No github release, so exiting after building" 62 | exit 0 63 | fi 64 | 65 | echo "### Commit appcast" 66 | if [[ "${update_appcast}" == true ]]; then 67 | if [[ "${is_snapshot}" == false ]]; then 68 | cp appcast.xml appcast_snapshot.xml 69 | fi 70 | 71 | git commit appcast* -m "Update appcast" 72 | git push 73 | fi 74 | 75 | popd >/dev/null 76 | 77 | echo "### Releasing VimR ended" 78 | } 79 | 80 | main 81 | 82 | -------------------------------------------------------------------------------- /bin/build_nightly_jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -Eeuo pipefail 3 | 4 | readonly branch=${branch:?"which branch to use"} 5 | 6 | upload_artifact() { 7 | local -r vimr_artifact_path="$1" 8 | local -x GH_TOKEN 9 | GH_TOKEN=$(cat ~/.local/secrets/github.qvacua.release.token) 10 | readonly GH_TOKEN 11 | 12 | echo "### Uploading artifact" 13 | gh release upload "neovim-nightly" "${vimr_artifact_path}" --clobber 14 | echo "### Uploaded artifact" 15 | } 16 | 17 | main() { 18 | echo "### Releasing nightly VimR started" 19 | 20 | pushd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 21 | 22 | git submodule update --init 23 | git tag -f neovim-nightly; git push -f origin neovim-nightly 24 | 25 | echo "### Build VimR" 26 | clean=true notarize=true ./bin/build_vimr.sh 27 | 28 | pushd ./build/Build/Products/Release >/dev/null 29 | tar cjf "VimR-nightly.tar.bz2" VimR.app 30 | upload_artifact "VimR-nightly.tar.bz2" 31 | popd >/dev/null 32 | 33 | popd >/dev/null 34 | 35 | echo "### Releasing nightly VimR ended" 36 | } 37 | 38 | main 39 | 40 | 41 | -------------------------------------------------------------------------------- /bin/build_nvimserver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -Eeuo pipefail 3 | 4 | # This script prepares Neovim binary and the runtime files for building VimR. 5 | # For most cases, you can just download the pre-built universal Neovim releases by running 6 | # `clean=true for_dev=false ./bin/neovim/bin/download_neovim_releases.sh` 7 | # If you want to build Neovim locally, use `for_dev=true`, then, the Neovim binary will be 8 | # built for the current architecture only and using the simple `make` command. 9 | 10 | declare -r -x clean=${clean:-true} 11 | declare -r -x for_dev=${for_dev:-false} 12 | 13 | main() { 14 | pushd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 15 | 16 | local -r resources_folder="./NvimView/Sources/NvimView/Resources" 17 | rm -rf "${resources_folder}/NvimServer" 18 | rm -rf "${resources_folder}/runtime" 19 | 20 | if [[ "${clean}" == true ]]; then 21 | pushd ./Neovim >/dev/null 22 | rm -rf .deps 23 | rm -rf build 24 | make distclean 25 | popd >/dev/null 26 | 27 | rm -rf "${resources_folder}/NvimServer" 28 | rm -rf "${resources_folder}/runtime" 29 | fi 30 | 31 | if [[ "${for_dev}" == true ]]; then 32 | 33 | ./bin/neovim/bin/build_neovim_for_dev.sh 34 | 35 | pushd ./Neovim/build >/dev/null 36 | local arch; arch="$(uname -m)"; readonly arch 37 | tar -xf "nvim-macos-${arch}.tar.gz" 38 | popd >/dev/null 39 | 40 | cp "./Neovim/build/nvim-macos-${arch}/bin/nvim" "${resources_folder}/NvimServer" 41 | cp -r "./Neovim/build/nvim-macos-${arch}/share/nvim/runtime" "${resources_folder}" 42 | 43 | else 44 | 45 | local neovim_release; neovim_release=$(jq -r ".neovimRelease" ./bin/neovim/resources/buildInfo.json) 46 | readonly neovim_release 47 | 48 | pushd ./Neovim >/dev/null 49 | mkdir -p build 50 | pushd ./build >/dev/null 51 | curl -LO "https://github.com/qvacua/vimr/releases/download/${neovim_release}/nvim-macos-universal.tar.bz" 52 | tar -xf nvim-macos-universal.tar.bz 53 | popd >/dev/null 54 | popd >/dev/null 55 | 56 | cp ./Neovim/build/nvim-macos-universal/bin/nvim "${resources_folder}/NvimServer" 57 | cp -r ./Neovim/build/nvim-macos-universal/share/nvim/runtime "${resources_folder}" 58 | 59 | fi 60 | 61 | # Copy VimR specific vim file to runtime/plugin folder 62 | cp "${resources_folder}/com.qvacua.NvimView.vim" "${resources_folder}/runtime/plugin" 63 | 64 | popd >/dev/null 65 | } 66 | 67 | main 68 | -------------------------------------------------------------------------------- /bin/build_vimr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -Eeuo pipefail 3 | 4 | readonly strip_symbols=${strip_symbols:-true} 5 | readonly notarize=${notarize:?"true or false"} 6 | readonly clean=${clean:?"true or false"} 7 | 8 | build_vimr() { 9 | local -r build_path=$1 10 | 11 | echo "### Xcodebuilding" 12 | rm -rf "${build_path}" 13 | if [[ "${clean}" == true ]]; then 14 | xcodebuild \ 15 | -configuration Release -derivedDataPath "${build_path}" \ 16 | -workspace VimR.xcworkspace -scheme VimR \ 17 | clean build 18 | else 19 | xcodebuild \ 20 | -configuration Release -derivedDataPath "${build_path}" \ 21 | -workspace VimR.xcworkspace -scheme VimR \ 22 | build 23 | fi 24 | } 25 | 26 | main () { 27 | pushd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 28 | echo "### Building VimR" 29 | 30 | ./bin/build_nvimserver.sh 31 | 32 | local -r build_path="./build" 33 | build_vimr "${build_path}" 34 | 35 | local -r -x vimr_app_path="${build_path}/Build/Products/Release/VimR.app" 36 | 37 | if [[ "${strip_symbols}" == true ]]; then 38 | strip -rSTx "${vimr_app_path}/Contents/MacOS/VimR" 39 | strip -rSx "${vimr_app_path}/Contents/Resources/NvimView_NvimView.bundle/Contents/Resources/NvimServer" 40 | fi 41 | 42 | if [[ "${notarize}" == true ]]; then 43 | ./bin/sign_vimr.sh 44 | ./bin/notarize_vimr.sh 45 | fi 46 | 47 | echo "### VimR built in ${build_path}/Build/Products/Release/VimR.app" 48 | popd >/dev/null 49 | } 50 | 51 | main 52 | -------------------------------------------------------------------------------- /bin/generate_autocmds.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import io 5 | import re 6 | from string import Template 7 | 8 | # Assume that we're in $REPO_ROOT/Neovim 9 | 10 | NVIM_AUEVENTS_ENUM_FILE = "./build/include/auevents_enum.generated.h" 11 | SWIFT_TEMPLATE_FILE = "../resources/autocmds.template.swift" 12 | 13 | 14 | def convert(line: str) -> tuple[str, str]: 15 | result = re.match(r'^EVENT_(.*) = (.*)', line.replace(',', '')) 16 | return result.group(1), result.group(2) 17 | 18 | 19 | def swift_autocmds(version: str, template_string: str) -> str: 20 | with io.open(NVIM_AUEVENTS_ENUM_FILE, "r") as auto_cmds_file: 21 | raw_auto_cmds = [line.strip() for line in auto_cmds_file.readlines() if re.match(r'^EVENT_', line.strip())] 22 | 23 | autocmds = [convert(line) for line in raw_auto_cmds] 24 | template = Template(template_string) 25 | 26 | return template.substitute( 27 | event_cases="\n".join( 28 | [f" case {event[0].lower()}" for event in autocmds] 29 | ), 30 | version=version 31 | ) 32 | 33 | 34 | if __name__ == '__main__': 35 | version = os.environ['version'] 36 | with io.open(SWIFT_TEMPLATE_FILE, "r") as template: 37 | print(swift_autocmds(version, template.read())) 38 | -------------------------------------------------------------------------------- /bin/generate_cursor_shape.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import io 4 | import os 5 | import re 6 | 7 | from string import Template 8 | 9 | # Assume that we're in $REPO_ROOT/Neovim 10 | 11 | NVIM_CURSOR_SHAPE_ENUM_FILE = "./src/nvim/cursor_shape.h" 12 | SWIFT_TEMPLATE_FILE = "../resources/cursor_shape.template.swift" 13 | 14 | SHAPE_NAMES = { 15 | "SHAPE_IDX_N": (0, "normal"), 16 | "SHAPE_IDX_V": (1, "visual"), 17 | "SHAPE_IDX_I": (2, "insert"), 18 | "SHAPE_IDX_R": (3, "replace"), 19 | "SHAPE_IDX_C": (4, "cmdlineNormal"), 20 | "SHAPE_IDX_CI": (5, "cmdlineInsert"), 21 | "SHAPE_IDX_CR": (6, "cmdlineReplace"), 22 | "SHAPE_IDX_O": (7, "operatorPending"), 23 | "SHAPE_IDX_VE": (8, "visualExclusive"), 24 | "SHAPE_IDX_CLINE": (9, "onCmdline"), 25 | "SHAPE_IDX_STATUS": (10, "onStatusLine"), 26 | "SHAPE_IDX_SDRAG": (11, "draggingStatusLine"), 27 | "SHAPE_IDX_VSEP": (12, "onVerticalSepLine"), 28 | "SHAPE_IDX_VDRAG": (13, "draggingVerticalSepLine"), 29 | "SHAPE_IDX_MORE": (14, "more"), 30 | "SHAPE_IDX_MOREL": (15, "moreLastLine"), 31 | "SHAPE_IDX_SM": (16, "showingMatchingParen"), 32 | "SHAPE_IDX_TERM": (17, "terminalMode"), 33 | "SHAPE_IDX_COUNT": (18, "count"), 34 | } 35 | 36 | 37 | def are_shapes_same() -> bool: 38 | with io.open(NVIM_CURSOR_SHAPE_ENUM_FILE, "r") as cursor_shape_header: 39 | shape_regex = r'^\s*(SHAPE_IDX_[A-Z]+)\s*= ([0-9]+)' 40 | shape_lines = [re.match(shape_regex, line) for line in cursor_shape_header] 41 | nvim_shapes = [m.groups() for m in shape_lines if m] 42 | 43 | return set(nvim_shapes) == set([(k, str(v[0])) for (k, v) in SHAPE_NAMES.items()]) 44 | 45 | 46 | def swift_shapes() -> str: 47 | with io.open(SWIFT_TEMPLATE_FILE, "r") as template_file: 48 | template = Template(template_file.read()) 49 | cases = "\n".join([f" case {v[1]}" for (k, v) in SHAPE_NAMES.items()]) 50 | return template.substitute( 51 | cursor_shapes=cases, 52 | version=version 53 | ) 54 | 55 | 56 | if __name__ == "__main__": 57 | version = os.environ['version'] 58 | assert are_shapes_same() 59 | 60 | print(swift_shapes()) 61 | -------------------------------------------------------------------------------- /bin/generate_sources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -Eeuo pipefail 3 | 4 | clean=${clean:?"true or false"} 5 | readonly use_committed_nvim=${use_committed_nvim:?"If true, checkout the committed version of nvim, otherwise use the workspace."} 6 | 7 | main() { 8 | echo "### Generating autocmds file." 9 | echo "* use_committed_nvim=$use_committed_nvim" 10 | pushd "$( dirname "${BASH_SOURCE[0]}" )/.." > /dev/null 11 | 12 | if [[ "${use_committed_nvim}" == true ]]; then 13 | echo "### Using the committed version of neovim." 14 | git submodule update 15 | else 16 | echo "### Using the workspace neovim." 17 | fi 18 | 19 | pushd Neovim > /dev/null 20 | major=$(grep -e "set(NVIM_VERSION_MAJOR" CMakeLists.txt | gsed -E "s/.* ([0-9]+)\)/\1/") 21 | minor=$(grep -e "set(NVIM_VERSION_MINOR" CMakeLists.txt | gsed -E "s/.* ([0-9]+)\)/\1/") 22 | patch=$(grep -e "set(NVIM_VERSION_PATCH" CMakeLists.txt | gsed -E "s/.* ([0-9]+)\)/\1/") 23 | prerelease=$(grep -e "set(NVIM_VERSION_PRERELEASE" CMakeLists.txt | gsed -E "s/.*\(.*\"(.*)\"\).*/\1/") 24 | nvim_version="v$major.$minor.$patch$prerelease" 25 | echo "### Using nvim version: $nvim_version" 26 | 27 | for_dev=true ../bin/build_nvimserver.sh 28 | 29 | version=${nvim_version} ../bin/generate_autocmds.py > "../NvimView/Sources/NvimView/NvimAutoCommandEvent.generated.swift" 30 | version=${nvim_version} ../bin/generate_cursor_shape.py > "../NvimView/Sources/NvimView/NvimCursorModeShape.generated.swift" 31 | swiftformat "../NvimView/Sources/NvimView/NvimAutoCommandEvent.generated.swift" 32 | swiftformat "../NvimView/Sources/NvimView/NvimCursorModeShape.generated.swift" 33 | popd > /dev/null 34 | 35 | clean=false ./RxPack/bin/generate_sources.sh 36 | 37 | popd > /dev/null 38 | echo "### Successfully generated autocmds." 39 | } 40 | 41 | main 42 | -------------------------------------------------------------------------------- /bin/neovim/bin/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /bin/neovim/bin/build_neovim_for_dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -Eeuo pipefail 3 | 4 | # This script builds Neovim with gettext for host's architecture, *no* universal build 5 | # Produces /Neovim/build/neovim-macos-$arch.tar.gz 6 | 7 | readonly clean=${clean:?"true or false"} 8 | readonly NVIM_BUILD_TYPE=${NVIM_BUILD_TYPE:-"Release"} 9 | 10 | build_neovim() { 11 | # slightly modified version of Neovim's Github workflow for release 12 | local -r -x MACOSX_DEPLOYMENT_TARGET=$1 13 | local -x SDKROOT; SDKROOT=$(xcrun --sdk macosx --show-sdk-path); readonly SDKROOT 14 | 15 | # Brew's gettext does not get sym-linked to PATH 16 | export PATH="/opt/homebrew/opt/gettext/bin:/usr/local/opt/gettext/bin:${PATH}" 17 | 18 | make CMAKE_BUILD_TYPE="${NVIM_BUILD_TYPE}" 19 | cpack --config build/CPackConfig.cmake 20 | } 21 | 22 | main() { 23 | # This script is located in /bin/neovim/bin and we have to go to / 24 | pushd "$(dirname "${BASH_SOURCE[0]}")/../../../" >/dev/null 25 | 26 | local deployment_target 27 | deployment_target=$(jq -r .deploymentTarget ./bin/neovim/resources/buildInfo.json) 28 | readonly deployment_target 29 | 30 | pushd ./Neovim >/dev/null 31 | echo "### Building neovim binary" 32 | if [[ "${clean}" == true ]]; then 33 | make distclean 34 | fi 35 | 36 | build_neovim "${deployment_target}" 37 | popd >/dev/null 38 | 39 | popd >/dev/null 40 | } 41 | 42 | main 43 | 44 | -------------------------------------------------------------------------------- /bin/neovim/bin/build_universal_neovim.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -Eeuo pipefail 3 | 4 | # This script creates a universal build, incl. Treesitter `so`s. The Treesitter 5 | # libs are put under the `runtime` folder instead of under `lib`. 6 | # 7 | # It expects to find the following files in the workspace root: 8 | # - nvim-macos-x86_64.tar.gz 9 | # - nvim-macos-arm64.tar.gz 10 | # It will produce the following in the workspace root: 11 | # - nvim-macos-universal.tar.bz 12 | # 13 | # To be used in the context of Github actions 14 | 15 | main() { 16 | # This script is located in /bin/neovim/bin and we have to go to / 17 | pushd "$(dirname "${BASH_SOURCE[0]}")/../../../" >/dev/null 18 | 19 | tar -xf nvim-macos-x86_64.tar.gz 20 | tar -xf nvim-macos-arm64.tar.gz 21 | 22 | mkdir -p "nvim-macos-universal" 23 | 24 | local universal_folder_path; universal_folder_path="$(pwd)/nvim-macos-universal"; 25 | readonly universal_folder_path 26 | echo "${universal_folder_path}" 27 | ls -la 28 | 29 | mkdir -p "${universal_folder_path}/bin" 30 | cp -r nvim-macos-arm64/share "${universal_folder_path}" 31 | mkdir -p "${universal_folder_path}/share/nvim/runtime/parser" 32 | 33 | lipo -create nvim-macos-arm64/bin/nvim nvim-macos-x86_64/bin/nvim \ 34 | -output "${universal_folder_path}/bin/nvim" 35 | for f in nvim-macos-arm64/lib/nvim/parser/*; do 36 | f="${f%/}" 37 | local filename="${f##*/}" 38 | lipo -create "nvim-macos-arm64/lib/nvim/parser/${filename}" \ 39 | "nvim-macos-x86_64/lib/nvim/parser/${filename}" \ 40 | -output "${universal_folder_path}/share/nvim/runtime/parser/${filename}" 41 | done 42 | 43 | tar -cjf nvim-macos-universal.tar.bz nvim-macos-universal 44 | 45 | popd >/dev/null 46 | } 47 | 48 | main 49 | -------------------------------------------------------------------------------- /bin/neovim/resources/NvimServer.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.disable-executable-page-protection 10 | 11 | com.apple.security.cs.disable-library-validation 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /bin/neovim/resources/buildInfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "deploymentTarget": "12", 3 | "neovimRelease": "neovim-v0.11.2-20250531.215520" 4 | } 5 | -------------------------------------------------------------------------------- /bin/notarize_vimr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -Eeuo pipefail 3 | 4 | readonly vimr_app_path=${vimr_app_path:?"Path to VimR.app"} 5 | 6 | main() { 7 | pushd "${vimr_app_path}/.." >/dev/null 8 | echo "### Notarizing" 9 | ditto -c -k --keepParent VimR.app VimR.app.zip 10 | 11 | echo "#### Notarizing" 12 | xcrun notarytool submit VimR.app.zip \ 13 | --keychain-profile "apple-dev-notar" \ 14 | --wait 15 | popd >/dev/null 16 | 17 | pushd "${vimr_app_path}/.." >/dev/null 18 | xcrun stapler staple VimR.app 19 | echo "### Notarization finished" 20 | popd >/dev/null 21 | } 22 | 23 | main 24 | -------------------------------------------------------------------------------- /bin/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | Markdown 3 | -------------------------------------------------------------------------------- /bin/resources: -------------------------------------------------------------------------------- 1 | ../resources -------------------------------------------------------------------------------- /bin/set_appcast.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pip3 install requests 4 | # pip3 install Markdown 5 | 6 | # We use python of brew due to some pip packages. 7 | 8 | import io 9 | import json 10 | import subprocess 11 | import sys 12 | from datetime import datetime 13 | from string import Template 14 | 15 | import markdown 16 | import requests 17 | 18 | SIGN_UPDATE = './build/SourcePackages/artifacts/sparkle/Sparkle/bin/sign_update' 19 | 20 | file_path = sys.argv[1] 21 | bundle_version = sys.argv[2] 22 | marketing_version = sys.argv[3] 23 | tag_name = sys.argv[4] 24 | is_snapshot = True if len(sys.argv) > 5 and sys.argv[5] == "true" else False 25 | 26 | file_signature = subprocess.check_output([SIGN_UPDATE, file_path]).decode('utf-8').strip() 27 | 28 | appcast_template_file = open('resources/appcast_template.xml', 'r') 29 | appcast_template = Template(appcast_template_file.read()) 30 | appcast_template_file.close() 31 | 32 | release_response = requests.get('https://api.github.com/repos/qvacua/vimr/releases/tags/{0}'.format(tag_name)) 33 | release_json = json.loads(release_response.content) 34 | 35 | title = release_json['name'] 36 | download_url = release_json['assets'][0]['browser_download_url'] 37 | release_notes_url = release_json['html_url'] 38 | release_notes = release_json['body'] 39 | 40 | appcast = appcast_template.substitute( 41 | title=title, 42 | release_notes=markdown.markdown(release_notes), 43 | release_notes_link=release_notes_url, 44 | publication_date=datetime.now().isoformat(), 45 | file_url=download_url, 46 | bundle_version=bundle_version, 47 | marketing_version=marketing_version, 48 | signature_output=file_signature 49 | ) 50 | 51 | appcast_file_name = 'appcast_snapshot.xml' if is_snapshot else 'appcast.xml' 52 | 53 | with io.open('build/Build/Products/Release/{0}'.format(appcast_file_name), 'w+') as appcast_file: 54 | appcast_file.write(appcast) 55 | -------------------------------------------------------------------------------- /bin/setup_markdown_css.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -Eeuo pipefail 3 | 4 | readonly version="5.0.0" 5 | readonly url="https://github.com/sindresorhus/github-markdown-css/archive/refs/tags/v${version}.tar.gz" 6 | readonly ref_md5="91db7943196075d6790c76fa184591d0" 7 | 8 | main() { 9 | pushd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 10 | 11 | local existing_md5; existing_md5="$(md5 -q ./VimR/VimR/markdown/github-markdown.css || echo "no file")"; readonly existing_md5 12 | if [[ "${existing_md5}" == "${ref_md5}" ]]; then 13 | echo "### CSS already exists, exiting" 14 | popd >/dev/null 15 | exit 0 16 | fi 17 | 18 | echo "### Downloading CSS and copying" 19 | local temp_dir; temp_dir="$(mktemp -d)"; readonly temp_dir 20 | echo "${temp_dir}" 21 | 22 | pushd "${temp_dir}" >/dev/null 23 | curl -s -L "${url}" -o "css.tar.gz" 24 | tar -xf css.tar.gz 25 | popd >/dev/null 26 | 27 | cp "${temp_dir}/github-markdown-css-${version}/github-markdown.css" ./VimR/VimR/markdown 28 | 29 | popd >/dev/null 30 | } 31 | 32 | main 33 | 34 | -------------------------------------------------------------------------------- /bin/sign_vimr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -Eeuo pipefail 3 | 4 | readonly vimr_app_path=${vimr_app_path:?"Path to VimR.app"} 5 | readonly identity="Developer ID Application: Tae Won Ha (H96Q2NKTQH)" 6 | 7 | remove_sparkle_xpc () { 8 | # VimR is not sandboxed, so, remove the XPCs 9 | # https://sparkle-project.org/documentation/sandboxing/#removing-xpc-services 10 | rm -rf "${vimr_app_path}/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/org.sparkle-project.InstallerLauncher.xpc" 11 | rm -rf "${vimr_app_path}/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/org.sparkle-project.Downloader.xpc" 12 | } 13 | 14 | main () { 15 | pushd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 16 | echo "### Signing VimR" 17 | local entitlements_path 18 | entitlements_path=$(realpath ./bin/neovim/resources/NvimServer.entitlements) 19 | readonly entitlements_path 20 | 21 | remove_sparkle_xpc 22 | 23 | codesign --verbose --force -s "${identity}" --timestamp --options=runtime \ 24 | "${vimr_app_path}/Contents/Frameworks/Sparkle.framework/Versions/B/Autoupdate" 25 | 26 | codesign --verbose --force -s "${identity}" --deep --timestamp --options=runtime \ 27 | "${vimr_app_path}/Contents/Frameworks/Sparkle.framework/Versions/B/Updater.app" 28 | 29 | codesign --verbose --force -s "${identity}" --options=runtime \ 30 | "${vimr_app_path}/Contents/Frameworks/Sparkle.framework" 31 | 32 | codesign --verbose --force -s "${identity}" --timestamp --options=runtime \ 33 | --entitlements="${entitlements_path}" \ 34 | "${vimr_app_path}/Contents/Resources/NvimView_NvimView.bundle/Contents/Resources/NvimServer" 35 | 36 | for f in "${vimr_app_path}/Contents/Resources/NvimView_NvimView.bundle/Contents/Resources/runtime/parser"/*; do 37 | codesign --verbose --force -s "${identity}" --timestamp --options=runtime \ 38 | "${f}" 39 | done 40 | 41 | codesign --verbose --force -s "${identity}" --deep --timestamp --options=runtime \ 42 | "${vimr_app_path}" 43 | 44 | echo "### Signed VimR" 45 | echo "### Use 'spctl -a -vvvv ${vimr_app_path}' to verify the signing." 46 | popd >/dev/null 47 | } 48 | 49 | main 50 | -------------------------------------------------------------------------------- /ci/README.md: -------------------------------------------------------------------------------- 1 | * Install Jenkins (via brew) 2 | * Install plugins 3 | - Job DSL 4 | - AnsiColor 5 | * Set the `git` binary in *Manage Jenkins* -> *Global Tool Configuration* 6 | * Set `PATH` for Jenkins (necessary for e.g. `git-lfs`) in *Manage Jenkins* -> *Configure System* -> *Global properties* -> *Environment variables" 7 | * Add a free style job `vimr_setup_jobs` with one step to process a Job DSL file at `ci/create_build_job.groovy`. 8 | - Approve script at *Manager Jenkins* -> *In-process Script Approval*. 9 | 10 | --- 11 | 12 | To test the job creation using local git repository, use `file:///Users/.../vimr-repo` as repository and add "-Dhudson.plugins.git.GitSCM.ALLOW_LOCAL_CHECKOUT=true" to `/opt/homebrew/opt/jenkins/bin/jenkins`: 13 | 14 | ```bash 15 | #!/bin/bash 16 | export JAVA_HOME="${JAVA_HOME:-/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home}" 17 | exec "${JAVA_HOME}/bin/java" "-Dhudson.plugins.git.GitSCM.ALLOW_LOCAL_CHECKOUT=true" "-jar" "/opt/homebrew/Cellar/jenkins/2.435/libexec/jenkins.war" "$@" 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /ci/create_build_job.groovy: -------------------------------------------------------------------------------- 1 | // Install the following plugins in addition to recommended plugins when installing Jenkins 2 | // - Job DSL 3 | // - AnsiColor 4 | 5 | def releaseVimRJob = freeStyleJob('vimr_release') 6 | def nightlyVimRJob = freeStyleJob('vimr_nightly') 7 | 8 | releaseVimRJob.with { 9 | description 'Release a new version' 10 | 11 | logRotator { 12 | numToKeep(10) 13 | } 14 | 15 | parameters { 16 | stringParam('marketing_version', null, 'Eg "0.34.0". If "is_snapshot" is unchecked, you have to enter this.') 17 | booleanParam('is_snapshot', true) 18 | stringParam('branch', 'master', 'Branch to build; defaults to master') 19 | textParam('release_notes', null, 'Release notes') 20 | booleanParam('create_gh_release', false, 'Publish this release to Github?') 21 | booleanParam('upload', false, 'Upload VimR to Github?') 22 | booleanParam('update_appcast', false) 23 | } 24 | 25 | scm { 26 | git { 27 | remote { 28 | url('git@github.com:qvacua/vimr.git') 29 | } 30 | branch('*/${branch}') 31 | } 32 | } 33 | 34 | wrappers { 35 | colorizeOutput() 36 | } 37 | 38 | steps { 39 | shell('./bin/build_jenkins.sh') 40 | } 41 | 42 | publishers { 43 | archiveArtifacts { 44 | pattern('Neovim/build/**, build/Build/Products/Release/**, release.spec.sh, release-notes.temp.md, appcast*, build_release.temp.sh') 45 | } 46 | } 47 | } 48 | 49 | nightlyVimRJob.with { 50 | description 'Release nightly' 51 | 52 | logRotator { 53 | numToKeep(10) 54 | } 55 | 56 | parameters { 57 | stringParam('branch', 'update-neovim', 'Branch to build; defaults to update-neovim') 58 | } 59 | 60 | scm { 61 | git { 62 | remote { 63 | url('git@github.com:qvacua/vimr.git') 64 | } 65 | branch('*/${branch}') 66 | } 67 | } 68 | 69 | wrappers { 70 | colorizeOutput() 71 | } 72 | 73 | steps { 74 | shell('./bin/build_nightly_jenkins.sh') 75 | } 76 | 77 | publishers { 78 | archiveArtifacts { 79 | pattern('Neovim/build/**, build/Build/Products/Release/**') 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /resources/advanced-pref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/resources/advanced-pref.png -------------------------------------------------------------------------------- /resources/appcast_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | VimR with NeoVim 5 | https://twitter.com/vimrefined 6 | Most recent changes with links to updates for VimR. 7 | en 8 | 9 | ${title} 10 | https://twitter.com/vimrefined 11 | ${bundle_version} 12 | ${marketing_version} 13 | 16 | 17 | ${release_notes_link} 18 | 19 | ${publication_date} 20 | 12.0 21 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /resources/appearance-pref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/resources/appearance-pref.png -------------------------------------------------------------------------------- /resources/autocmds.template.swift: -------------------------------------------------------------------------------- 1 | // Auto generated for nvim ${version} 2 | // See bin/generate_autocmds.py 3 | 4 | enum NvimAutoCommandEvent: String { 5 | 6 | ${event_cases} 7 | } 8 | -------------------------------------------------------------------------------- /resources/copy-cli-tool-preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/resources/copy-cli-tool-preferences.png -------------------------------------------------------------------------------- /resources/cursor_shape.template.swift: -------------------------------------------------------------------------------- 1 | // Auto generated for nvim ${version} 2 | // See bin/generate_cursor_shape.py 3 | 4 | public enum CursorModeShape: String { 5 | 6 | ${cursor_shapes} 7 | } 8 | -------------------------------------------------------------------------------- /resources/ligatures-preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/resources/ligatures-preferences.png -------------------------------------------------------------------------------- /resources/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/resources/screenshot1.png -------------------------------------------------------------------------------- /resources/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/resources/screenshot2.png -------------------------------------------------------------------------------- /resources/shortcuts-pref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/resources/shortcuts-pref.png -------------------------------------------------------------------------------- /resources/snapshot-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/resources/snapshot-update.png -------------------------------------------------------------------------------- /resources/vimr-app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qvacua/vimr/a42732bdf0959a4203c0873db90599f403919a6c/resources/vimr-app-icon.png --------------------------------------------------------------------------------