I have used quite a few environments for programming. Starting with Windows and CodeBlocks few years back, then upgrading it a little bit by switching to Visual Studio Code, to which I stuck for a while. At some point, my laptop could not handle all the programs I want to use, so I switched to Arch Linux on some random night, which was not a pleasant night, as I did not use any linux before. Xfce desktop environment took some weight of my laptop’s shoulders as it decreased ram usage to < 300MB. I am not gonna lie, it was quite a challenge to use it at the beggining, but it became - after few tears and sleepless nights - awesome, and I never even considered going back to Windows.

Finally, when things with Flutter got more serious, I had to buy Macbook (Air M1 2020), so I can develop mobile apps for iOS too. I am using it till this day! Despite the fact, that I have tried to use Neovim on Arch Linux, when I switched to MacOS, I’ve started using JetBrains IDEs. I had stronger hardware, so I finally could run it. I can’t say bad word about these IDEs, I really enjoyed using them - everything was there out of the box. (un)Fortunately, when I started my full time job, I needed to change it, as I did not want to spend money for a license. I really hate VSCode, so it was a simple choice - Neovim. That’s how my journey with environment started. In this post I will try to showcase its current state, and I really hope you can find something here to boost your day-to-day programming experience!

How does it look?Link to this heading

Terminal (iTerm2)Link to this heading

Nothing fancy to say about this, it is just fun, simple and fast. I once tried a terminal written in Electron, never again. I think I have only opacity configured, to see my call wallpaper while coding.

Zsh (Shell)Link to this heading

I use oh-my-zsh plugin, for a better experience with auto-completion & syntax highlighting. Also git plugin and vi mode come in handy.

bash
# dotfiles/.zshrc

export ZSH="$HOME/.oh-my-zsh"
ZSH_THEME="robbyrussell"

plugins=(git)
plugins+=(zsh-vi-mode)

source $ZSH/oh-my-zsh.sh
source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh

Include Zellij config and start it by default

bash
export ZELLIJ_CONFIG_DIR="$DOTFILES/zellij/"

source ~/dotfiles/zellij/.zellij.conf

if [[ -z "$ZELLIJ" ]]; then
    zellij attach default
fi

Besides that, I’ve got quite a few aliases and exports, but I am not going to bore yall with that here.

Okay, maybe just these three as these are stupid but cool.

bash
alias b="cd .."
alias bb="cd ../.."
alias bbb="cd ../../.."

I really recommend to setup some aliases, they save so much time in a long run. For example, instead of running

flutter run --flavor=development --target=lib/main_development.dart"

every single time, you can just alias it to rundev

Zellij (Terminal Workspace)Link to this heading

Zellij is a extremely cool terminal workspace (terminal multiplexer). I used tmux for a short while but it did not really clicked with me. On the other hand, I can’t live without Zellij. It allows you to have different sessions - between which you can switch easily in runtime -, retains them and makes it really convinient to work with tabs and panes.

The default keybinds were enough for me - I needed to change only one, but I can not recall which one was it to be honest.

Neovim (Code Editor)Link to this heading

Now, fun begins.

I won’t go through my entire nvim config, but I will share the essentials.

Plugins:

bash
# full list available in file
# dotfiles/nvim/lua/plugins/plugins_list.lua

# Basically everything you need for flutter
'akinsho/flutter-tools.nvim',

# Language Server Protocol configurator
'neovim/nvim-lspconfig'

# Powerful Fuzzy Finder
'nvim-telescope/telescope.nvim',

# Comment out blocks of code
'numToStr/Comment.nvim',

# Displays float with info who commited given line
'rhysd/git-messenger.vim'

# Completion
'hrsh7th/nvim-cmp'
'hrsh7th/cmp-nvim-lsp'

# Display errors/warnings/info
"folke/trouble.nvim"

# Snippets
"L3MON4D3/LuaSnip"

# Togglable file tree
'nvim-tree/nvim-tree.lua'

# Togglable terminal
'akinsho/toggleterm.nvim'

# Opens floating window with curent buffer, to investigate some
# code without losing contest
"carbon-steel/detour.nvim"

# Debugger and its UI 
'mfussenegger/nvim-dap'
'rcarriga/nvim-dap-ui'

Setting up support for flutter is as easy as this:

c
require "flutter-tools".setup {
    lsp = {
        color = {
            enabled = true,
        },
        settings = {
            showTodos = true,
            completeFunctionCalls = true,
            // enable analysis in flutter repo
            analysisExcludedFolders = {},
        }
    }
}

Flutter codebase I work on daily is not that small - over 50k lines of code and constantly increasing. At the beginning, it was not easy to navigate through it quickly in Neovim but once I got more fluent in Telescope it is great. Nvim-tree comes in handy quite often too. It is extremely cool, that I can find a file/directory and its place in the file tree using vim shortcuts.

Debugging was also a challenge, a big one actually. At first, I was just opening VSCode for a second, to debug something, as it have better support out of the box. After some time, I properly configured nvim-dap-ui, which is a UI for the debugger, and it was a game-changer. Finally I could uninstall VSCode, as its debugger is not even close as good.

First required thing is to configure flutter adapter and setup its configurations:

c
// dotfiles/nvim/lua/plugins/nvim-dap.lua

-- Exported in shell config file
local dart_path = os.getenv("DART_PATH");
local flutter_path = os.getenv("FLUTTER_PATH");

dap.configurations.dart = {
    {
        type = "flutter",
        request = "launch",
        name = "Launch Flutter | Development",
        dartSdkPath = dart_path,
        flutterSdkPath = flutter_path,
        program = "${workspaceFolder}/lib/main_development.dart",
        cwd = "${workspaceFolder}",
        toolArgs = function()
            local default_flutter_device = os.getenv("DEFAULT_FLUTTER_DEVICE");
            local selected_device = default_flutter_device;
            if not selected_device then
                selected_device = get_device();
            end

            return { "-d", selected_device, "--flavor", "development" };
        end

    },
    -- Add other configs here, like production, staging etc.

get_device() is my helper function, that allow me to choose on which device to run. It runs flutter devices underneath, analyzes the output and displays a floating window with possible options. Unfortunately - as we know -, running flutter devices is quite slow, and it blocks nvim for that time. Setting DEFAULT_FLUTTER_DEVICE env in shell config may be helpful, unless you are changing yours simulators very often.

c
// dotfiles/nvim/lua/plugins/nvim-dap.lua

local get_device = function()
    local co = coroutine.running()
    local output = ""
    local exit_code = 0
    local on_stdout = function(_, data)
        if data then
            for _, line in ipairs(data) do
                if string.match(line, '•') then
                    output = output .. line .. '\n'
                end
            end
        end
    end

    vim.print("Running flutter devices...")
    local job_id = vim.fn.jobstart('flutter devices', {
        stdout_buffered = true,
        on_stdout = on_stdout,
        on_stderr = on_stdout,
        on_exit = function(_, code, _)
            exit_code = code
        end,
    })
    vim.fn.jobwait({ job_id })
    if exit_code ~= 0 then
        return vim.print("Failed to retrieve device")
    end

    vim.ui.select(vim.fn.split(output, '\n'), {
        prompt = "Select Device",
        telescope = require 'telescope.themes'.get_cursor(),
    }, function(selected)
        coroutine.resume(co, selected)
    end
    );
    local selected_device = coroutine.yield()
    return vim.fn.trim(vim.fn.split(selected_device, '•')[2])
end

I have a keymap for starting debugger set to <leader>dc (where leader is space for me), so if I want to start debugging, I just type space dc, select which configuration (development/mock/…), then I select (unless default specified, which is usually the case) which one of available devices to use, and Voila!

Debugging starts, I can toggle debugging ui with <leader>dut and start to play with it!

Keymaps are not restricted to plugins functions only, thus you can add some cool helpers, such as:

c
map('n', '<leader>df', ':!dart format -l 120 %<CR>')

The line above, allows you to write space df to run the customized formatting on current file

I’ve got quite a few keymaps, but the most essential are those:

bash
# dotfiles/nvim/lua/keymappings.lua

map('n', '<space>gl', vim.diagnostic.open_float)
map('n', 'gd', vim.lsp.buf.definition, opts)
map('n', 'K', vim.lsp.buf.hover, opts)
map('n', '<space>rn', vim.lsp.buf.rename, opts)
map({ 'n', 'v' }, '<space>a', vim.lsp.buf.code_action, opts)
map('n', '<leader>e', ':NvimTreeToggle<CR>')
map("n", "<leader>xw", function() trouble.open("workspace_diagnostics") end)
map("n", "gr", function() trouble.open("lsp_references") end)
map('n', '<leader>tr', ':ToggleTerm<CR>')
map('n', '<leader>dd', ":Detour<cr>")
map('n', '<leader>dbp', ':DapToggleBreakpoint<CR>')
map('n', '<leader>dtr', ':DapToggleRepl<CR>')
map('n', '<leader>dc', ':DapContinue<CR>')
map('n', '<leader>ff', function() telescope.find_files() end)
map('n', '<leader>fg', function() telescope.live_grep() end)
map('n', '<leader>fb', function() telescope.buffers() end)
map('n', '<leader>dut', function() dapui.toggle() end)
map('n', '<leader>duf', function() dapui.float_element() end)

That is basically all! Of course, there are much more things in my config, but I believe it is a cool sneak peak. I hope you found something interesting to add to your config, if you aready use neovim, if not, I hope you will consider it!

The entire config of my dotfiles can be found on my Github - here