Xcode Neovim Replacement-ish.

Last update: Jun 21, 2022

drawing

An XCode replacement-ish development environment that aims to be your reliable XCode alternative to develop exciting new [apple] software products ๐Ÿš€ .

Table of Content

๐Ÿ‘ Overview

XBase enables you to build, watch, and run xcode products from within your favorite editor. It supports running products on iOS, watchOS and tvOS simulators, along with real-time logging, and some lsp features such as auto-completion and code navigation. ( ๐ŸŒŸ Features).

Furthermore, XBase has built-in support for a variety of XCode project generators, which allow you to avoid launching XCode or manually editing '*.xcodeproj' anytime you add or remove files. We strongly advise you to use one ... at least till XBase supports adding/removing files and folders, along with other requirements. ( ๐Ÿ’† Generators)

  • Watch XBase repo to remain up to date on fresh improvements and exciting new features.
  • Checkout Milestones for planned future features and releases.
  • Visit CONTRIBUTING.md to have your setup to start contributing and support the project.

Please be aware that XBase is still WIP, so don't hesitate to report bugs, ask questions or suggest new exciting features.

๐ŸŒ Motivation

I chose to dive into iOS/macOS app development after purchasing an M1 MacBook. However, coming from vim/shell environment and being extremely keyboard oriented, I couldn't handle the transition to a closed sourced, opinionated, mouse-driven development environment. I've considered alternatives like XVim2 and the built-in vim emulator, however still, I'd catch myself frequently hunting for my mouse.

As a long-time vim user who has previously developed a several lua/nvim plugins, I decided to invest some effort in simplifying my development workflow for producing 'xOS' products.

๐ŸŒŸ Features

  • Auto-Completion and Code navigation
    Auto-generate compilation database on directory changes + a custom build server that assists sourcekit-lsp in providing code navigation and auto-completion for project symbols.
  • Multi-nvim instance support
    Multiple nvim instance support without process duplications and shared state. For instance, you can stop a watch service that was being run from a different instance.
  • Auto-start/stop main background daemon
    Daemon will start and stop automatically based on the number of connected client instances.
  • Multi Target/Project Support
    Work on multiple projects at one nvim instance at the same time. TODO
  • Simulator Support
    Run your products on simulators relative to your target's platform. (+ watch build and ran on change)
  • Runtime/Build Logging
    Real-time logging of build logs and 'print()' commands
  • Statusline Support
    Global variable to update statusline with build/run commands, see Statusline
  • Zero Footprint
    Light resource usage. I've been using XBase for a while; it typically uses 0.1 percent RAM and 0 percent CPU.
  • Multi XCodeProj Support
    Auto-generate xcodeproj, when it doesn't exists, generator config files a updated or new files/directories added or removed.

๐Ÿ’† Generators

XBase primarily supports two project generators: XcodeGen and Tuist.

XCodeGen is recommended if you are just starting started with xcodeproj generators since it is considerably simpler with a yml-based configuration language. Having said that, Tuist is more powerful and packed with features, of which xcodeproj generation is but one.

XBase's support for generators is available in the following forms:

  • Identification.
  • Auto-generate xcodeproj if you haven't haven't generate it by hand.
  • Auto-generate xcodeproj when you edit the generator config files.
  • Auto-compile project when xcodeproj get regenerated.
  • Code Completion and navigation (#tuist)

Limitations

XCodeGen

  • No support for custom named yml config files, only project.yml.

Other Generators

With current XBase architecture, it should be pretty easy to add support for yet another awesome xcodeproj generator. feel free to get started with CONTRIBUTING.md or open a github issue

๐Ÿ›  Requirements

๐Ÿฆพ Installation

To install XBase on your system you need run make install. This will run cargo build --release on all the required binaries in addition to a lua library. The binaries will be moved to path/to/repo/bin and the lua library will be moved to path/to/repo/lua/libxbase.so.

With packer

use {
  'tami5/xbase',
    run = 'make install',
    requires = {
      "nvim-lua/plenary.nvim",
      "nvim-telescope/telescope.nvim"
    },
    config = function()
      require'xbase'.setup({})  -- see default configuration bellow
    end
}

With vim-plug

Plug 'nvim-lua/plenary.nvim'
Plug 'nvim-telescope/telescope.nvim'
Plug 'tami5/xbase', { 'do': 'make install' }
lua require'xbase'.setup()

With dein

call dein#add('nvim-lua/plenary.nvim')
call dein#add('nvim-telescope/telescope.nvim')
call dein#add('tami5/xbase', { 'build': 'make install' })
lua require'xbase'.setup()

๐ŸŽฎ Usage

TLDR:

  • Install XBase
  • run require'xbase'.setup({ --[[ see default configuration ]] })
  • Open xcodeproj codebase.
  • Wait for first time project setup finish.
  • Start coding
  • Use available actions which can be configure with shortcuts bellow

When you start a neovim instance with a root that contains project.yml, Project.swift, or *.xcodeproj, the daemon server will auto-start if no instance is running, and register the project once for recompile-watch. To communicate with your deamon, checkout the configurable shortcuts.

Statusline

XBase provide feline provider, other statusline plugins support are welcomed. However, using vim.g.xbase_watch_build_status you can easily setup statusline indicators.

require("xbase.util").feline_provider() -- append to feline setup function

โš™๏ธ Defaults

-- NOTE: Defaults
{
  --- Log level. Set to error to ignore everything: { "trace", "debug", "info", "warn", "error" }
  log_level = "debug",
  --- Default log buffer direction: { "horizontal", "vertical", "float" }
  default_log_buffer_direction = "horizontal",
  --- Statusline provider configurations
  statusline = {
    watching = { icon = "๏‘", color = "#1abc9c" },
    running = { icon = "โš™", color = "#e0af68" },
    device_running = { icon = "๏”ด", color = "#4a6edb" },
    success = { icon = "๏…Š", color = "#1abc9c" },
    failure = { icon = "๏™™", color = "#db4b4b" },
    show_progress = false,
  },
  --- TODO(nvim): Limit devices platform to select from
  simctl = {
    iOS = {
      "iPhone 13 Pro",
      "iPad (9th generation)",
    },
  },
  mappings = {
    --- Whether xbase mapping should be disabled.
    enable = true,
    --- Open build picker. showing targets and configuration.
    build_picker = "<leader>b", --- set to 0 to disable
    --- Open run picker. showing targets, devices and configuration
    run_picker = "<leader>r", --- set to 0 to disable
    --- Open watch picker. showing run or build, targets, devices and configuration
    watch_picker = "<leader>s", --- set to 0 to disable
    --- A list of all the previous pickers
    all_picker = "<leader>ef", --- set to 0 to disable
    --- horizontal toggle log buffer
    toggle_split_log_buffer = "<leader>ls",
    --- vertical toggle log buffer
    toggle_vsplit_log_buffer = "<leader>lv",
  }
}

๐Ÿฉบ Debugging

Read logs

# Daemon logs
tail -f /tmp/xbase-daemon.log
# Build Server logs
tail -f /tmp/xbase-server.log

๐ŸŽฅ Preview

Watch build service.

On error it opens a log buffer where you can inspect what went wrong, otherwise only the statusline get updated.

GitHub

https://github.com/tami5/xbase
Comments
  • 1. `make install` error

    Hello, does it work for now? I got an error when execute make install.

    make install
        Updating git repository `https://github.com/tami5/simctl`
        Updating crates.io index
      Downloaded ppv-lite86 v0.2.16
      Downloaded itertools v0.10.3
      Downloaded regex v1.5.6
      Downloaded strum v0.24.0
      Downloaded nom-supreme v0.6.0
      Downloaded shell-words v1.1.0
      Downloaded strum_macros v0.24.0
      Downloaded tracing v0.1.34
      Downloaded anyhow v1.0.57
      Downloaded async-stream v0.3.3
      Downloaded rand_core v0.5.1
      Downloaded rustversion v1.0.6
      Downloaded ryu v1.0.10
      Downloaded matches v0.1.9
      Downloaded num_cpus v1.13.1
      Downloaded aho-corasick v0.7.18
      Downloaded thiserror-impl v1.0.31
      Downloaded async-stream-impl v0.3.3
      Downloaded dirs v4.0.0
      Downloaded brownstone v1.1.0
      Downloaded async-trait v0.1.53
      Downloaded const_format_proc_macros v0.2.22
      Downloaded getrandom v0.1.16
      Downloaded bsp-types v0.1.3
      Downloaded matchers v0.1.0
      Downloaded joinery v2.1.0
      Downloaded rand v0.7.3
      Downloaded parity-tokio-ipc v0.9.0
      Downloaded xcodebuild v0.1.6
      Downloaded tokio-macros v1.7.0
      Downloaded same-file v1.0.6
      Downloaded arrayvec v0.7.2
      Downloaded futures-task v0.3.21
      Downloaded ansi_term v0.12.1
      Downloaded serde_yaml v0.8.24
      Downloaded tinyvec v1.6.0
      Downloaded sharded-slab v0.1.4
      Downloaded bitflags v1.3.2
      Downloaded cfg-if v1.0.0
      Downloaded futures-sink v0.3.21
      Downloaded futures-core v0.3.21
      Downloaded time v0.3.9
      Downloaded futures v0.3.21
      Downloaded log v0.4.17
      Downloaded crossbeam-utils v0.8.8
      Downloaded url v2.2.2
      Downloaded crossbeam-channel v0.5.4
      Downloaded wax v0.4.0
      Downloaded lsp-types v0.93.0
      Downloaded indent_write v2.2.0
      Downloaded tracing-core v0.1.26
      Downloaded form_urlencoded v1.0.1
      Downloaded autocfg v1.1.0
      Downloaded tap v1.0.1
      Downloaded percent-encoding v2.1.0
      Downloaded lazy_static v1.4.0
      Downloaded dirs-sys v0.3.7
      Downloaded either v1.6.1
      Downloaded heck v0.4.0
      Downloaded futures-macro v0.3.21
      Downloaded num_threads v0.1.6
      Downloaded futures-executor v0.3.21
      Downloaded linked-hash-map v0.5.4
      Downloaded nom v7.1.1
      Downloaded mio v0.8.3
      Downloaded bytes v1.1.0
      Downloaded unicode-ident v1.0.0
      Downloaded futures-channel v0.3.21
      Downloaded indexmap v1.8.1
      Downloaded futures-util v0.3.21
      Downloaded serde v1.0.137
      Downloaded regex-automata v0.1.10
      Downloaded const_format v0.2.23
      Downloaded minimal-lexical v0.2.1
      Downloaded itoa v1.0.2
      Downloaded once_cell v1.12.0
      Downloaded pin-utils v0.1.0
      Downloaded pin-project-lite v0.2.9
      Downloaded proc-macro2 v1.0.39
      Downloaded quote v1.0.18
      Downloaded slab v0.4.6
      Downloaded memchr v2.5.0
      Downloaded rand_chacha v0.2.2
      Downloaded futures-io v0.3.21
      Downloaded serde_repr v0.1.8
      Downloaded serde_json v1.0.81
      Downloaded signal-hook-registry v1.4.0
      Downloaded serde_derive v1.0.137
      Downloaded pori v0.0.0
      Downloaded syn v1.0.95
      Downloaded socket2 v0.4.4
      Downloaded tokio-stream v0.1.8
      Downloaded tinyvec_macros v0.1.0
      Downloaded thread_local v1.1.4
      Downloaded derive-deref-rs v0.1.1
      Downloaded thiserror v1.0.31
      Downloaded smallvec v1.8.0
      Downloaded unicode-bidi v0.3.8
      Downloaded bsp-server v0.1.3
      Downloaded process-stream v0.2.2
      Downloaded hashbrown v0.11.2
      Downloaded regex-syntax v0.6.26
      Downloaded idna v0.2.3
      Downloaded bstr v0.2.17
      Downloaded tracing-subscriber v0.3.11
      Downloaded yaml-rust v0.4.5
      Downloaded tracing-log v0.1.3
      Downloaded unicode-normalization v0.1.19
      Downloaded tracing-attributes v0.1.21
      Downloaded walkdir v2.3.2
      Downloaded tracing-appender v0.2.2
      Downloaded unicode-xid v0.2.3
      Downloaded tokio v1.18.2
      Downloaded libc v0.2.126
      Downloaded 114 crates (6.4 MB) in 12.02s
       Compiling proc-macro2 v1.0.39
       Compiling unicode-ident v1.0.0
       Compiling syn v1.0.95
       Compiling libc v0.2.126
       Compiling memchr v2.5.0
       Compiling cfg-if v1.0.0
       Compiling lazy_static v1.4.0
       Compiling pin-project-lite v0.2.9
       Compiling futures-core v0.3.21
       Compiling log v0.4.17
       Compiling serde_derive v1.0.137
       Compiling once_cell v1.12.0
       Compiling futures-task v0.3.21
       Compiling serde v1.0.137
       Compiling regex-syntax v0.6.26
       Compiling futures-channel v0.3.21
       Compiling itoa v1.0.2
       Compiling futures-util v0.3.21
       Compiling tinyvec_macros v0.1.0
       Compiling getrandom v0.1.16
       Compiling futures-sink v0.3.21
       Compiling futures-io v0.3.21
       Compiling matches v0.1.9
       Compiling crossbeam-utils v0.8.8
       Compiling pin-utils v0.1.0
       Compiling slab v0.4.6
       Compiling unicode-bidi v0.3.8
       Compiling bytes v1.1.0
       Compiling serde_json v1.0.81
       Compiling anyhow v1.0.57
       Compiling ryu v1.0.10
       Compiling percent-encoding v2.1.0
       Compiling minimal-lexical v0.2.1
       Compiling ppv-lite86 v0.2.16
       Compiling autocfg v1.1.0
       Compiling rustversion v1.0.6
       Compiling smallvec v1.8.0
       Compiling async-trait v0.1.53
       Compiling arrayvec v0.7.2
       Compiling unicode-xid v0.2.3
       Compiling bitflags v1.3.2
       Compiling ansi_term v0.12.1
       Compiling joinery v2.1.0
       Compiling linked-hash-map v0.5.4
       Compiling heck v0.4.0
       Compiling same-file v1.0.6
       Compiling either v1.6.1
       Compiling indent_write v2.2.0
       Compiling hashbrown v0.11.2
       Compiling tap v1.0.1
       Compiling shell-words v1.1.0
       Compiling tracing-core v0.1.26
       Compiling sharded-slab v0.1.4
       Compiling thread_local v1.1.4
       Compiling tinyvec v1.6.0
       Compiling form_urlencoded v1.0.1
       Compiling indexmap v1.8.1
       Compiling brownstone v1.1.0
       Compiling walkdir v2.3.2
       Compiling yaml-rust v0.4.5
       Compiling itertools v0.10.3
       Compiling regex-automata v0.1.10
       Compiling tracing-log v0.1.3
       Compiling unicode-normalization v0.1.19
       Compiling aho-corasick v0.7.18
       Compiling nom v7.1.1
       Compiling matchers v0.1.0
       Compiling bstr v0.2.17
       Compiling quote v1.0.18
       Compiling mio v0.8.3
       Compiling num_cpus v1.13.1
       Compiling signal-hook-registry v1.4.0
       Compiling socket2 v0.4.4
       Compiling num_threads v0.1.6
       Compiling dirs-sys v0.3.7
       Compiling crossbeam-channel v0.5.4
       Compiling idna v0.2.3
       Compiling regex v1.5.6
       Compiling const_format_proc_macros v0.2.22
       Compiling rand_core v0.5.1
       Compiling pori v0.0.0
       Compiling nom-supreme v0.6.0
       Compiling time v0.3.9
       Compiling dirs v4.0.0
       Compiling rand_chacha v0.2.2
       Compiling rand v0.7.3
       Compiling const_format v0.2.23
       Compiling futures-macro v0.3.21
       Compiling tracing-attributes v0.1.21
       Compiling tokio-macros v1.7.0
       Compiling thiserror-impl v1.0.31
       Compiling serde_repr v0.1.8
       Compiling async-stream-impl v0.3.3
       Compiling strum_macros v0.24.0
       Compiling derive-deref-rs v0.1.1
       Compiling async-stream v0.3.3
       Compiling tokio v1.18.2
       Compiling thiserror v1.0.31
       Compiling wax v0.4.0
       Compiling tracing v0.1.34
       Compiling strum v0.24.0
       Compiling tracing-subscriber v0.3.11
       Compiling tracing-appender v0.2.2
       Compiling futures-executor v0.3.21
       Compiling tokio-stream v0.1.8
       Compiling futures v0.3.21
       Compiling process-stream v0.2.2
       Compiling parity-tokio-ipc v0.9.0
       Compiling xcodebuild v0.1.6
    error[E0554]: `#![feature]` may not be used on the stable release channel
     --> /Users/xpeng/.cargo/registry/src/github.com-1ecc6299db9ec823/xcodebuild-0.1.6/src/lib.rs:2:12
      |
    2 | #![feature(str_split_whitespace_as_str)]
      |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
       Compiling url v2.2.2
    For more information about this error, try `rustc --explain E0554`.
    error: could not compile `xcodebuild` due to previous error
    warning: build failed, waiting for other jobs to finish...
    make: *** [install] Error 101
    
    Reviewed by TMTBO at 2022-06-10 07:41
  • 2. Readme: mention how to setup xbase plugin

    Sorry if I'm abusing the issue tracker here, but either something is broken or I don't know what I'm doing.

    I added the plugin via vim-plug like this, and it appears to install just fine (make install succeeds at least):

    call plug#begin('~/.vim/plugged')
    Plug 'nvim-lua/plenary.nvim'
    Plug 'nvim-telescope/telescope.nvim'
    Plug 'tami5/xbase', { 'do': 'make install' }
    call plug#end()
    

    The project is built with xcodegen and has a project.yml file at it's root. But when I edit a file in the project directory, nothing happens. There's also no xbase log or socket opened in /tmp.

    Am I missing a config setting somewhere? I realize I installed this with vim-plug rather than packer, but I would have expected it to work the same.

    Reviewed by michaelnew at 2022-06-17 21:48
  • 3. chore(deps): bump wax from 0.4.0 to 0.5.0

    Bumps wax from 0.4.0 to 0.5.0.

    Commits
    • 8dba278 Bump version to 0.5.0.
    • 5bc4f27 Consolidate glob expression build errors.
    • 0487b44 Gate Variance behind the diagnostics-inspect feature.
    • 2ac3120 Update documentation.
    • 1522366 Deny some pedantic clippy lints.
    • 64a8f00 Associate depth with the computed root directory.
    • 1c72837 Merge pull request #21 from olson-sean-k/dependabot/cargo/nom-supreme-tw-0.8.0
    • c4bfe20 Update nom-supreme requirement from ^0.7.0 to ^0.8.0
    • b1af159 Rename Walk::for_each to Walk::for_each_ref.
    • 54586ac Use more precise conditional compilation attributes.
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Reviewed by dependabot[bot] at 2022-06-19 16:47
  • 4. support barebone xcodeproj

    Although intentionally I wanted to stay away from supporting a non-generative Xcode project due to how it require reopening Xcode every time the project working directory structure changes, but this is something important, it would be nice to just clone a xcodeproj and be able to navigate it.

    Maybe there is a tool that would enable wave the requirement of reopening Xcode project when the directory change. We'll see.

    • [ ] Parse .xcodeproj and extract targets, schemas,
    • [x] #45
    • [ ] Make Project and enum with XBare variant indicating an non-generative xcodeproj.
    Reviewed by tami5 at 2022-05-31 11:01
  • 5. [logger] append title

    https://github.com/tami5/xbase.nvim/blob/724589988bb422e4bdcc6aa68869b2053fe5eea6/src/nvim/logger.rs#L44

    
            })
        }
    
        // TODO(logger): append title
        pub async fn log(&mut self, msg: String) -> Result<()> {
            tracing::debug!("{msg}");
    
    
    
    Reviewed by github-actions[bot] at 2022-05-23 18:28
  • 6. support custom index store

    / Matching r"^CompileSwiftSources\s*"

    / Matching "^CompileSwift\s+ \w+\s+ \w+\s+ (.+)$"

    https://github.com/tami5/XcodeBase.nvim/blob/a87a4ff04529327cc5d8f8093e91118478b6c44b/shared/src/xcode/compilation.rs#L13

    
    // CREDIT: @SolaWing https://github.com/SolaWing/xcode-build-server/blob/master/xcode-build-server
    // CREDIT: Richard Howell https://github.com/doc22940/sourcekit-lsp/blob/master/Tests/INPUTS/BuildServerBuildSystemTests.testBuildTargetOutputs/server.py
    
    mod command;
    use anyhow::Result;
    use command::CompilationCommand;
    use lazy_static::lazy_static;
    use regex::Regex;
    use std::collections::HashMap;
    
    // TODO: Support compiling commands for objective-c files
    // TODO: Test multiple module command compile
    // TODO: support index store
    
    pub struct Compiliation {
        pub commands: Vec<CompilationCommand>,
        lines: Vec<String>,
        clnum: usize,
        index_store_path: Vec<String>,
    }
    
    impl Compiliation {
        pub fn new(build_log: Vec<String>) -> Self {
            let mut parser = Self {
                lines: build_log,
                clnum: 0,
                commands: Vec::default(),
                index_store_path: Vec::default(),
            };
    
            for line in parser.lines.iter() {
                parser.clnum += 1;
    
                if line.starts_with("===") {
                    continue;
                }
    
                if RE["swift_module"].is_match(line) {
                    if let Some(command) = parser.swift_module_command() {
                        if let Some(isp) = &command.index_store_path {
                            parser.index_store_path.push(isp.clone())
                        }
                        parser.commands.push(command);
                        continue;
                    };
                }
            }
            parser
        }
    
        /// Serialize to JSON string
        pub fn to_json(&self) -> Result<String, serde_json::Error> {
            serde_json::to_string_pretty(&self.commands)
        }
    }
    
    lazy_static! {
        static ref RE: HashMap<&'static str, Regex> = HashMap::from([
            (
                "swift_module",
                Regex::new(r"^CompileSwiftSources\s*").unwrap()
            ),
            (
                "swift",
                Regex::new(r"^CompileSwift\s+ \w+\s+ \w+\s+ (.+)$").unwrap()
            )
        ]);
    }
    
    impl Compiliation {
        /// Parse starting from current line as swift module
        /// Matching r"^CompileSwiftSources\s*"
        fn swift_module_command(&self) -> Option<CompilationCommand> {
            let directory = match self.lines.get(self.clnum) {
                Some(s) => s.trim().replace("cd ", ""),
                None => {
                    tracing::error!("Found COMPILE_SWIFT_MODULE_PATERN but no more lines");
                    return None;
                }
            };
    
            let command = match self.lines.get(self.clnum + 3) {
                Some(s) => s.trim().to_string(),
                None => {
                    tracing::error!("Found COMPILE_SWIFT_MODULE_PATERN but couldn't extract command");
                    return None;
                }
            };
    
            match CompilationCommand::new(directory, command) {
                Ok(command) => {
                    tracing::debug!("Extracted {} Module Command", command.name);
                    Some(command)
                }
                Err(e) => {
                    tracing::error!("Fail to create swift module command {e}");
                    None
                }
            }
        }
    
        /// Parse starting from current line as swift module
        /// Matching "^CompileSwift\s+ \w+\s+ \w+\s+ (.+)$"
        #[allow(dead_code)]
        fn swift_command(&self, _line: &str) {}
    }
    
    #[test]
    fn test() {
        tokio::runtime::Runtime::new().unwrap().block_on(async {
            let build_log_test = tokio::fs::read_to_string("/Users/tami5/repos/swift/wordle/build.log")
                .await
                .unwrap()
                .split("\n")
                .map(|l| l.to_string())
                .collect();
            let compiliation = Compiliation::new(build_log_test);
    
            println!("{}", compiliation.to_json().unwrap())
        });
    }
    
    
    Reviewed by github-actions[bot] at 2022-04-17 22:18
  • 7. Remove wathcers for workspaces that are no longer exist

    /

    / Sometiems we get event for the same path, particularly

    / ModifyKind::Name::Any is ommited twice for the new path

    / and once for the old path.

    /

    / This will compare last_seen with path, updates last_seen if not match,

    / else returns true.

    https://github.com/tami5/XcodeBase.nvim/blob/a87a4ff04529327cc5d8f8093e91118478b6c44b/shared/src/watch.rs#L20

    
    use crate::state::SharedState;
    use crate::Command;
    use notify::{Error, Event, RecommendedWatcher, RecursiveMode, Watcher};
    use std::path::Path;
    use std::result::Result;
    use std::sync::Arc;
    use std::time::Duration;
    use tokio::sync::{mpsc, Mutex};
    use tracing::{debug, trace};
    use wax::{Glob, Pattern};
    
    // TODO: Stop handle
    
    pub async fn update(state: SharedState, _msg: Command) {
        let copy = state.clone();
        let mut current_state = copy.lock().await;
        let mut watched_roots: Vec<String> = vec![];
        let mut start_watching: Vec<String> = vec![];
    
        // TODO: Remove wathcers for workspaces that are no longer exist
    
        for key in current_state.watchers.keys() {
            watched_roots.push(key.clone());
        }
    
        for key in current_state.workspaces.keys() {
            if !watched_roots.contains(key) {
                start_watching.push(key.clone());
            }
        }
    
        for root in start_watching {
            let handle = new(state.clone(), root.clone());
            current_state.watchers.insert(root, handle);
        }
    }
    
    /// HACK: ignore seen paths.
    ///
    /// Sometiems we get event for the same path, particularly
    /// `ModifyKind::Name::Any` is ommited twice for the new path
    /// and once for the old path.
    ///
    /// This will compare last_seen with path, updates `last_seen` if not match,
    /// else returns true.
    async fn should_ignore(last_seen: Arc<Mutex<String>>, path: &str) -> bool {
        // HACK: Always return false for project.yml
        let path = path.to_string();
        if path.contains("project.yml") {
            return false;
        }
        let mut last_seen = last_seen.lock().await;
        if last_seen.to_string() == path {
            return true;
        } else {
            *last_seen = path;
            return false;
        }
    }
    
    // TODO: Cleanup get_ignore_patterns and decrease duplications
    async fn get_ignore_patterns(state: SharedState, root: &String) -> Vec<String> {
        let mut patterns: Vec<String> = vec![
            "**/.git/**",
            "**/*.xcodeproj/**",
            "**/.*",
            "**/build/**",
            "**/buildServer.json",
        ]
        .iter()
        .map(|e| e.to_string())
        .collect();
    
        // FIXME: Addding extra ignore patterns to `ignore` local config requires restarting deamon.
        let extra_patterns = state
            .lock()
            .await
            .workspaces
            .get(root)
            .unwrap()
            .get_ignore_patterns();
    
        if let Some(extra_patterns) = extra_patterns {
            patterns.extend(extra_patterns);
        }
    
        patterns
    }
    
    fn new(state: SharedState, root: String) -> tokio::task::JoinHandle<anyhow::Result<()>> {
        // NOTE: should watch for registerd directories?
        // TODO: Support provideing additional ignore wildcard
        //
        // Some files can be generated as direct result of running build command.
        // In my case this `Info.plist`.
        //
        // For example,  define key inside project.yml under xcodebase key, ignoreGlob of type array.
    
        tokio::spawn(async move {
            let (tx, mut rx) = mpsc::channel(100);
    
            let mut watcher = RecommendedWatcher::new(move |res: Result<Event, Error>| {
                if res.is_ok() {
                    tx.blocking_send(res.unwrap()).unwrap()
                };
            })?;
    
            watcher.watch(Path::new(&root), RecursiveMode::Recursive)?;
            watcher.configure(notify::Config::NoticeEvents(true))?;
    
            // HACK: ignore seen paths.
            let last_seen = Arc::new(Mutex::new(String::default()));
    
            // HACK: convert back to Vec<&str> for Glob to work.
            let patterns = get_ignore_patterns(state.clone(), &root).await;
            let patterns = patterns.iter().map(AsRef::as_ref).collect::<Vec<&str>>();
            let ignore = wax::any::<Glob, _>(patterns).unwrap();
    
            while let Some(event) = rx.recv().await {
                let state = state.clone();
                let path = match event.paths.get(0) {
                    Some(p) => p.clone(),
                    None => continue,
                };
    
                let path_string = match path.to_str() {
                    Some(s) => s.to_string(),
                    None => continue,
                };
    
                if ignore.is_match(&*path_string) {
                    continue;
                }
    
                // debug!("[FSEVENT] {:?}", &event);
                // NOTE: maybe better handle in tokio::spawn?
                match &event.kind {
                    notify::EventKind::Create(_) => {
                        tokio::time::sleep(Duration::new(1, 0)).await;
                        debug!("[FileCreated]: {:?}", path);
                    }
                    notify::EventKind::Remove(_) => {
                        tokio::time::sleep(Duration::new(1, 0)).await;
                        debug!("[FileRemoved]: {:?}", path);
                    }
                    notify::EventKind::Modify(m) => {
                        match m {
                            notify::event::ModifyKind::Data(e) => match e {
                                notify::event::DataChange::Content => {
                                    if !path_string.contains("project.yml") {
                                        continue;
                                    }
                                    tokio::time::sleep(Duration::new(1, 0)).await;
                                    debug!("[XcodeGenConfigUpdate]");
                                    // HACK: Not sure why, but this is needed because xcodegen break.
                                }
                                _ => continue,
                            },
                            notify::event::ModifyKind::Name(_) => {
                                // HACK: only account for new path and skip duplications
                                if !Path::new(&path).exists()
                                    || should_ignore(last_seen.clone(), &path_string).await
                                {
                                    continue;
                                }
                                tokio::time::sleep(Duration::new(1, 0)).await;
                                debug!("[FileRenamed]: {:?}", path);
                            }
                            _ => continue,
                        }
                    }
                    _ => continue,
                }
    
                trace!("[NewEvent] {:#?}", &event);
    
                // let mut state = state.lock().await;
    
                match state.lock().await.workspaces.get_mut(&root) {
                    Some(w) => {
                        w.on_dirctory_change(path, event.kind).await?;
                    }
                    // NOTE: should stop watch here
                    None => continue,
                };
            }
            Ok(())
        })
    }
    
    
    Reviewed by github-actions[bot] at 2022-04-17 22:18
  • 8. chore(deps): bump xclog from 0.2.6 to 0.2.7

    Bumps xclog from 0.2.6 to 0.2.7.

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Reviewed by dependabot[bot] at 2022-06-20 16:52
  • 9. [project] Get swift project name

    https://github.com/tami5/xbase/blob/7fb498cadc813df1941665398b5325a255d6015e/daemon/src/project/mod.rs#L67

    
    mod generator;
    
    use crate::device::Device;
    use crate::watch::Event;
    use crate::{state::State, Result};
    use generator::ProjectGenerator;
    use serde::{Deserialize, Serialize};
    use std::collections::HashMap;
    use std::path::PathBuf;
    use tokio::sync::MutexGuard;
    use xbase_proto::BuildSettings;
    use xcodeproj::pbxproj::PBXTargetPlatform;
    use xcodeproj::XCodeProject;
    
    /// Project Inner
    #[derive(Debug)]
    pub enum ProjectInner {
        None,
        XCodeProject(XCodeProject),
        Swift,
    }
    
    impl Default for ProjectInner {
        fn default() -> Self {
            Self::None
        }
    }
    
    /// Represent XcodeGen Project
    #[derive(Default, Debug, Deserialize, Serialize)]
    #[serde(rename_all = "camelCase")]
    pub struct Project {
        /// Project Name or rather xproj generated file name.
        pub name: String,
    
        /// The list of targets in the project mapped by name
        pub targets: HashMap<String, PBXTargetPlatform>,
    
        /// Root directory
        #[serde(skip)]
        pub root: PathBuf,
    
        /// Connected Clients
        #[serde(default)]
        pub clients: Vec<i32>,
    
        /// Ignore Patterns
        #[serde(default)]
        pub ignore_patterns: Vec<String>,
    
        /// Generator
        pub generator: ProjectGenerator,
    
        #[serde(skip)]
        /// XCodeProject Data
        inner: ProjectInner,
    }
    
    impl Project {
        pub async fn new(root: &std::path::PathBuf) -> Result<Self> {
            let mut project = Self::default();
    
            project.generator = ProjectGenerator::new(root);
    
            project.inner = if root.join("Package.swift").exists() {
                tracing::debug!("[Project] Kind: \"Swift\"",);
                // TODO(project): Get swift project name
                project.name = "UnknownSwiftProject".into();
                ProjectInner::Swift
            } else {
                let xcodeproj = XCodeProject::new(root)?;
                project.name = xcodeproj.name().to_owned();
                project.targets = xcodeproj.targets_platform();
    
                tracing::debug!("[New Project] name: {:?}", project.name);
                tracing::debug!("[New Project] Kind: \"XCodeProject\"");
                tracing::debug!("[New Project] Generator: \"{:?}\"", project.generator);
                tracing::debug!("[New Project] targets: {:?}", project.targets);
    
                ProjectInner::XCodeProject(xcodeproj)
            };
    
            project
                .ignore_patterns
                .extend(gitignore_to_glob_patterns(root).await?);
    
            project.ignore_patterns.extend(vec![
                "**/.git/**".into(),
                "**/.*".into(),
                "**/build/**".into(),
                "**/buildServer.json".into(),
    
    
    Reviewed by github-actions[bot] at 2022-06-19 22:05
  • 10. chore(deps): bump anyhow from 1.0.57 to 1.0.58

    Bumps anyhow from 1.0.57 to 1.0.58.

    Release notes

    Sourced from anyhow's releases.

    1.0.58

    • Fix some broken links in documentation
    Commits
    • 8f950ac Release 1.0.58
    • bf23b3b Merge pull request #242 from MedzikUser/master
    • d8c2388 Use upstreamed docs.rs icon in docs.rs badge
    • db82639 Fix broken doc links to anyhow macro
    • 462212b Update docs.rs badge
    • ff37db3 Check all crates in workspace for outdated deps
    • 302acad Match components in CI to rust-toolchain.toml file
    • 26023f7 Run miri in stricter miri-strict-provenance mode
    • f450657 Drop unneeded quoting from env variable in workflows yaml
    • 09e4db3 Update workflows to actions/[email protected]
    • See full diff in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Reviewed by dependabot[bot] at 2022-06-19 16:47
  • 11. [run] might want to keep track of ran services

    https://github.com/tami5/xbase.nvim/blob/a0014e0907ad6a5fb1268858d098a21350e05409/src/daemon/requests/run.rs#L35

    
        pub device: DeviceLookup,
        #[cfg_attr(feature = "daemon", serde(deserialize_with = "value_or_default"))]
        pub direction: BufferDirection,
        #[cfg_attr(feature = "daemon", serde(deserialize_with = "value_or_default"))]
        pub ops: RequestOps,
    }
    
    #[cfg(feature = "daemon")]
    #[async_trait::async_trait]
    impl Handler for RunRequest {
        async fn handle(self) -> Result<()> {
            let ref key = self.to_string();
            tracing::info!("โš™๏ธ Running: {}", self.config.to_string());
    
            let state = DAEMON_STATE.clone();
            let ref mut state = state.lock().await;
    
            if self.ops.is_once() {
                // TODO(run): might want to keep track of ran services
                RunService::new(state, self).await?;
                return Ok(());
            }
    
            let client = self.client.clone();
            if self.ops.is_watch() {
                let watcher = client.get_watcher(state)?;
                if watcher.contains_key(key) {
                    client
                        .nvim(state)?
                        .echo_err("Already watching with {key}!!")
                        .await?;
                } else {
                    let run_service = RunService::new(state, self).await?;
                    let watcher = client.get_watcher_mut(state)?;
                    watcher.add(run_service)?;
                }
            } else {
                let watcher = client.get_watcher_mut(state)?;
                let listener = watcher.remove(&self.to_string())?;
                listener.discard(state).await?;
            }
    
            state.sync_client_state().await?;
            Ok(())
        }
    }
    
    
    
    Reviewed by github-actions[bot] at 2022-05-25 19:37
  • 12. [compile] support jumping to external packages symbols

    I have no clue how this would be supported with [sourcekit-lsp] but it would be nice to have support to jumping to swift and external package definitions

    sourcekit-lsp

    Reviewed by tami5 at 2022-06-21 18:39
  • 13. [project] log build and compile logs to client

    https://github.com/tami5/xbase/blob/79ea745d5c673f1ff717bf3f097bc661713b562a/daemon/src/util/mod.rs#L11

    
    //! General utilities
    
    pub mod fmt;
    pub mod fs;
    pub mod pid;
    
    use crate::OutputStream;
    use process_stream::{ProcessItem, StreamExt};
    
    /// Consume given stream and return whether the stream exist with 0
    /// TODO(project): log build and compile logs to client
    pub async fn consume_and_log(mut stream: OutputStream) -> (bool, Vec<String>) {
        let mut success = false;
        let mut items = vec![];
        while let Some(output) = stream.next().await {
            if let ProcessItem::Exit(v) = output {
                success = v.eq("0");
            } else {
                log::debug!("{output}");
                items.push(output.to_string())
            }
        }
        (success, items)
    }
    
    
    Reviewed by github-actions[bot] at 2022-06-21 07:34
  • 14. support add / remove files from xcodeproj

    This requires supporting project.pbxproj writing in xcodeproj crate as well as defining some type of logic for adding new project, e.g. notifying the user that there is a new file and he must pick a target or a list of targets to add that file to.

    Reviewed by tami5 at 2022-06-19 22:02
  • 15. [watchignore] support ! patterns

    https://github.com/tami5/xbase/blob/0a2943b628cd0478156962569e6fdc812bd44421/daemon/src/util/fs.rs#L68

    
            .ok_or_else(|| anyhow::anyhow!("Fail to generate build_cache directory for {root_path:?}"))
    }
    
    pub fn get_build_cache_dir<P: AsRef<Path> + Debug>(root_path: P) -> Result<String> {
        _get_build_cache_dir(root_path, None)
    }
    
    pub fn get_build_cache_dir_with_config<P: AsRef<Path> + Debug>(
        root_path: P,
        config: &BuildSettings,
    ) -> Result<String> {
        _get_build_cache_dir(root_path, Some(config))
    }
    
    /// Read .gitignore from root and return vec of glob patterns if the .gitignore eixists.
    pub async fn gitignore_to_glob_patterns<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
        let gitignore_path = path.as_ref().join(".gitignore");
        if !gitignore_path.exists() {
            return Ok(Default::default());
        }
        let content = tokio::fs::read_to_string(gitignore_path).await?;
        Ok(gitignore_content_to_glob_patterns(content))
    }
    
    pub fn gitignore_content_to_glob_patterns(content: String) -> Vec<String> {
        content
            .split("\n")
            .filter(|s| !s.is_empty() && !s.starts_with("#"))
            .flat_map(|s| {
                if s.starts_with("!") {
                    None // TODO(watchignore): support ! patterns
                } else {
                    Some(("", s))
                }
            })
            .filter(|&(_, s)| s.chars().next() != Some('/'))
            .map(|(pat, s)| {
                if s != "/" {
                    (pat, format!("**/{s}"))
                } else {
                    (pat, format!("{s}"))
                }
            })
            .flat_map(|(pat, s)| {
                let pattern = format!("{pat}{s}");
                vec![pattern.clone(), format!("{pattern}/**")]
            })
            .collect::<Vec<String>>()
    }
    
    #[test]
    fn test_gitignore_patterns() {
        let gitignore_patterns =
            gitignore_content_to_glob_patterns(String::from(".build.log\n.compile"));
        assert_eq!(
            gitignore_patterns,
            vec![
                "**/.build.log".to_string(),
                "**/.build.log/**".to_string(),
                "**/.compile".to_string(),
                "**/.compile/**".to_string()
            ]
        );
    
        println!("{gitignore_patterns:#?}");
    }
    
    
    Reviewed by github-actions[bot] at 2022-06-17 11:31
๐Ÿ”ญ Search Dash.app from Neovim with Telescope. Built with Rust ๐Ÿฆ€ and Lua
๐Ÿ”ญ Search Dash.app from Neovim with Telescope. Built with Rust ๐Ÿฆ€ and Lua

Dash.nvim Query Dash.app within Neovim with a Telescope picker! The theme used in the recording is lighthaus.nvim. Note: Dash is a Mac-only app, so yo

Jun 19, 2022
Neovide - No Nonsense Neovim Client in Rust
Neovide - No Nonsense Neovim Client in Rust

Neovide This is a simple graphical user interface for Neovim (an aggressively refactored and updated Vim editor). Where possible there are some graphi

Jun 25, 2022
An async autocompletion framework for Neovim
An async autocompletion framework for Neovim

โšก nvim-compleet This plugin is still in early development. ?? Table of Contents Installation Features Configuration Sources Commands Mappings Colors R

Jun 20, 2022
exa is a modern replacement for ls.
exa is a modern replacement for ls.

exa exa is a modern replacement for ls. README Sections: Options โ€” Installation โ€” Development exa is a modern replacement for the venerable file-listi

Jun 22, 2022
zoxide is a blazing fast replacement for your cd command
zoxide is a blazing fast replacement for your cd command

zoxide A smarter cd command for your terminal zoxide is a blazing fast replacement for your cd command, inspired by z and z.lua. It keeps track of the

Jun 18, 2022
procs is a replacement for ps written in Rust.
procs is a replacement for ps written in Rust.

procs is a replacement for ps written in Rust. Documentation quick links Features Platform Installation Usage Configuration Features Output by t

Jun 23, 2022
fastmod is a fast partial replacement for the codemod tool

fastmod is a fast partial replacement for codemod. Like codemod, it is a tool to assist you with large-scale codebase refactors, and it supports most of codemod's options.

Jun 25, 2022
A drop-in replacement for `dapp` and `seth` in Rust

dapptools.rs Rust port of DappTools dapp example Usage Run Solidity tests Any contract that contains a function starting with test is being tested. Th

Jun 19, 2022
A readline replacement written in Rust

A readline replacement written in Rust Basic example // Create a default reedline object to handle user input use reedline::{DefaultPrompt, Reedline,

Jun 15, 2022
Fls - Ferris-LS, a very bad LS replacement. Besides that, proves that I suck at clean & good code.

FLS A handy ls remake, purely for learning. Why FLS? There's no reason, at all, don't use it. I just want to learn Rust :D Usage Flags: -i = Use icons

Jan 24, 2022
๐Ÿฆ€๏ธatos for linux by rust - A partial replacement for Apple's atos tool for converting addresses within a binary file to symbols.

atosl-rs ??๏ธ atos for linux by rust - A partial replacement for Apple's atos tool for converting addresses within a binary file to symbols. tested on

Jun 4, 2022
xcp is a (partial) clone of the Unix cp command. It is not intended as a full replacement

xcp is a (partial) clone of the Unix cp command. It is not intended as a full replacement, but as a companion utility with some more user-friendly feedback and some optimisations that make sense under certain tasks (see below).

Jun 17, 2022
drop-in replacement for libfuzzer
drop-in replacement for libfuzzer

Fazi A reimplementation of libfuzzer in Rust with some improvements Supported Features libFuzzer's mutations SanCov feedback Building without a main()

Jun 13, 2022
Bundle Cargo crates for use with macOS/iOS in Xcode

cargo-cocoapods - Build Rust code for Xcode integration Installing cargo install cargo-cocoapods You'll also need to install all the toolchains you i

Apr 14, 2022
A low-ish level tool for easily writing and hosting WASM based plugins.

A low-ish level tool for easily writing and hosting WASM based plugins. The goal of wasm_plugin is to make communicating across the host-plugin bounda

Jun 3, 2022
YAML(ish) - Terminal UI framework based on templates focused on simplicity
YAML(ish) - Terminal UI framework based on templates focused on simplicity

A YAML(ish) based terminal GUI framework for- and by Rust, focussed on making it quick and easy to create a functional UI for an app or game. Based on Crossterm and inspired by Kivy.

May 24, 2022
Language Server Protocol (LSP) support for vim and neovim.
Language Server Protocol (LSP) support for vim and neovim.

For legacy python implementation, see branch master. LanguageClient-neovim Language Server Protocol support for vim and neovim. More recordings at Upd

Jun 24, 2022
Rust On the FLY completion for neovim

rofl.nvim Rust On the FLy completion engine for Neovim. Why Rust? It's 2021. I think the question you should be asking yourself is "Why NOT Rust?!?? (

May 31, 2022
N vim win! Neovim UI designed for Sway/i3.
N vim win! Neovim UI designed for Sway/i3.

Nwin This is an experimental Neovim UI that creates a new OS window for each Neovim window, all backed by the same neovim server. This enables perform

Jun 13, 2022
Just a bot for Neovim's Matrix room(s)

nvim-matrix-bot Currently just supports replying to messages with :h <some_doc> or similar in them with a link to the docs on Neovim's website. Plan i

Apr 1, 2022