Skip to content

5th February 2024

Life Management


Life planning

  • New: Update the month planning.

    Decide the month objectives:

    Create the month objectives in your roadmap file after addressing each element of:

    • Your last month review document.
    • The trimester objectives of your roadmap.

    Once they are ready, copy them to the description of your file. That way you'll see it every day.

    For each of your month objectives:

    • Decide whether it makes sense to address it this month. If not, archive it
    • Create a clear plan of action for this month on that objective
    • Define the todo of each device (mobile, tablet, laptop)
    • Tweak your things to think about list.
    • Tweak your reading list.
    • Tweak your week distribution (what to do in each day)
    • If you selected maintenance week days tweak the priorities on your maintenance list.
    • If you selected improvement week days tweak the priorities on your improvements list.
    • Tweak your habit manager system.

Life review

  • New: Use deadlines.

    Identify hard deadlines: Add a warning days before the deadline to make sure you're reminded until it's done.

  • New: Install using lazyvim.

    return {
            dependencies = {
          { 'nvim-treesitter/nvim-treesitter', lazy = true },
            event = 'VeryLazy',
            config = function()
        -- Load treesitter grammar for org
        -- Setup treesitter
            highlight = {
              enable = true,
              additional_vim_regex_highlighting = { 'org' },
            ensure_installed = { 'org' },
        -- Setup orgmode
            org_agenda_files = '~/orgfiles/**/*',
            org_default_notes_file = '~/orgfiles/',
      dependencies = {
        { 'nvim-treesitter/nvim-treesitter', lazy = true },
      event = 'VeryLazy',
      config = function()
        -- Load treesitter grammar for org
        -- Setup treesitter
          highlight = {
            enable = true,
            additional_vim_regex_highlighting = { 'org' },
          ensure_installed = { 'org' },
        -- Setup orgmode
          org_agenda_files = '~/orgfiles/**/*',
          org_default_notes_file = '~/orgfiles/',
  • New: Troubleshoot orgmode with dap.

    Use the next config and follow the steps of Create an issue in the orgmode repository.

    vim.cmd([[set runtimepath=$VIMRUNTIME]])
    vim.cmd([[set packpath=/tmp/nvim/site]])
    local package_root = '/tmp/nvim/site/pack'
    local install_path = package_root .. '/packer/start/packer.nvim'
    local function load_plugins()
          { 'nvim-treesitter/nvim-treesitter' },
          { 'nvim-lua/plenary.nvim'},
          { 'nvim-orgmode/orgmode'},
          { 'nvim-telescope/telescope.nvim'},
          { 'lyz-code/telescope-orgmode.nvim' },
          { 'jbyuki/one-small-step-for-vimkind' },
          { 'mfussenegger/nvim-dap' },
          { 'kristijanhusak/orgmode.nvim', branch = 'master' },
        config = {
          package_root = package_root,
          compile_path = install_path .. '/plugin/packer_compiled.lua',
    _G.load_config = function()
        highlight = {
          enable = true,
          additional_vim_regex_highlighting = { 'org' },
      vim.cmd([[packadd nvim-treesitter]])
      vim.cmd([[runtime plugin/nvim-treesitter.lua]])
      vim.cmd([[TSUpdateSync org]])
      -- Close packer after install
      if == 'packer' then
        vim.api.nvim_win_close(0, true)
        org_agenda_files = {
      -- Reload current file if it's org file to reload tree-sitter
      if == 'org' then
    if vim.fn.isdirectory(install_path) == 0 then
      vim.fn.system({ 'git', 'clone', '', install_path })
      vim.cmd([[autocmd User PackerCompileDone ++once lua load_config()]])
      defaults = {
        preview = {
         enable = true,
          treesitter = false,
        vimgrep_arguments = {
        file_ignore_patterns = {
        mappings = {
          i = {
            -- Required so that folding works when opening a file in telescope
            ["<CR>"] = function()
                vim.cmd [[:stopinsert]]
                vim.cmd [[call feedkeys("\<CR>")]]
            ['<C-j>'] = 'move_selection_next',
            ['<C-k>'] = 'move_selection_previous',
      pickers = {
        find_files = {
          find_command = { "rg", "--files", "--hidden", "--glob", "!**/.git/*" },
          hidden = true,
          follow = true,
      extensions = {
        fzf = {
          fuzzy = true,                    -- false will only do exact matching
          override_generic_sorter = true,  -- override the generic sorter
          override_file_sorter = true,     -- override the file sorter
          case_mode = "smart_case",        -- or "ignore_case" or "respect_case"
                                           -- the default case_mode is "smart_case"
        heading = {
          treesitter = true,
    local key = vim.keymap
    vim.g.mapleader = ' '
    local builtin = require('telescope.builtin')
    key.set('n', '<leader>f', builtin.find_files, {})
    key.set('n', '<leader>F', ':Telescope file_browser<cr>')
    vim.api.nvim_create_autocmd('FileType', {
      pattern = 'org',
      group = vim.api.nvim_create_augroup('orgmode_telescope_nvim', { clear = true }),
      callback = function()
        vim.keymap.set('n', '<leader>r', require('telescope').extensions.orgmode.refile_heading)
        vim.keymap.set('n', '<leader>g', require('telescope').extensions.orgmode.search_headings)
    local org = require('orgmode').setup({
      org_agenda_files = {
      org_todo_keywords = { 'TODO(t)', 'CHECK(c)', 'DOING(d)', 'RDEACTIVATED(r)', 'WAITING(w)', '|','DONE(e)', 'REJECTED(j)', 'DUPLICATE(u)' },
      org_hide_leading_stars = true,
      org_deadline_warning_days = 0,
      win_split_mode = "horizontal",
      org_priority_highest = 'A',
      org_priority_default = 'C',
      org_priority_lowest = 'D',
      mappings = {
        global = {
          org_agenda = 'ga',
          org_capture = ';c',
        org = {
          -- Enter new items
          org_meta_return = '<c-cr>',
          org_insert_heading_respect_content = ';<cr>',
          org_insert_todo_heading = "<c-t>",
          org_insert_todo_heading_respect_content = ";t",
          -- Heading promoting and demoting
          org_toggle_heading = '<leader>h',
          org_do_promote = '<h',
          org_do_demote = '>h',
          org_promote_subtree = '<H',
          org_demote_subtree = '>H',
          -- Heading moving
          org_move_subtree_up = "<leader>k",
          org_move_subtree_down = "<leader>j",
          -- Heading navigation
          org_next_visible_heading = '<c-j>',
          org_previous_visible_heading = '<c-k>',
          org_forward_heading_same_level = '<c-n>',
          org_backward_heading_same_level = '<c-p>',
          outline_up_heading = 'gp',
          org_open_at_point = 'gx',
          -- State transition
          org_todo = 't',
          -- Priority change
          org_priority_up = '-',
          org_priority_down = '=',
          -- Date management
          org_deadline = '<leader>d',
          org_schedule = '<leader>s',
          org_time_stamp = ';d',
          org_change_date = '<c-e>',
          -- Tag management
          org_set_tags_command = 'T',
          -- Archive management and refiling
          org_archive_subtree = ';a',
          org_toggle_archive_tag = ';A',
          -- org_refile = '<leader>r',  The refile is being handled below
        agenda = {
          org_agenda_later = '<c-n>',
          org_agenda_earlier = '<c-p>',
          org_agenda_switch_to = '<tab>',
          org_agenda_goto = '<cr>',
          org_agenda_priority_up = '=',
          org_agenda_set_tags = 'T',
          org_agenda_deadline = '<leader>d',
          org_agenda_schedule = '<leader>s',
          org_agenda_archive = 'a',
        capture = {
          org_capture_finalize = ';w',
          org_capture_refile = ';r',
          org_capture_kill = 'qqq',
    local dap = require"dap"
    dap.configurations.lua = {
        type = 'nlua',
        request = 'attach',
        name = "Attach to running Neovim instance",
    dap.adapters.nlua = function(callback, config)
      callback({ type = 'server', host = or "", port = config.port or 8086 })
    vim.api.nvim_set_keymap('n', '<leader>b', [[:lua require"dap".toggle_breakpoint()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<leader>c', [[:lua require"dap".continue()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<leader>n', [[:lua require"dap".step_over()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<leader>m', [[:lua require"dap"<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<leader>N', [[:lua require"dap".step_into()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<F12>', [[:lua require"dap.ui.widgets".hover()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<F5>', [[:lua require"osv".launch({port = 8086})<CR>]], { noremap = true })
  • New: Hide the state changes in the folds.

    The folding of the recurring tasks iterations is also kind of broken. For the next example

    ** TODO Recurring task
       DEADLINE: <2024-02-08 Thu .+14d -0d>
       :LAST_REPEAT: [2024-01-25 Thu 11:53]
       - State "DONE" from "TODO" [2024-01-25 Thu 11:53]
       - State "DONE" from "TODO" [2024-01-10 Wed 23:24]
       - State "DONE" from "TODO" [2024-01-03 Wed 19:39]
       - State "DONE" from "TODO" [2023-12-11 Mon 21:30]
       - State "DONE" from "TODO" [2023-11-24 Fri 13:10]
       - [ ] Do X

    When folded the State changes is not added to the Properties fold. It's shown something like:

    ** TODO Recurring task
       DEADLINE: <2024-02-08 Thu .+14d -0d>
       - State "DONE" from "TODO" [2024-01-25 Thu 11:53]
       - State "DONE" from "TODO" [2024-01-10 Wed 23:24]
       - State "DONE" from "TODO" [2024-01-03 Wed 19:39]
       - State "DONE" from "TODO" [2023-12-11 Mon 21:30]
       - State "DONE" from "TODO" [2023-11-24 Fri 13:10]
       - [ ] Do X

    I don't know if this is a bug or a feature, but when you have many iterations it's difficult to see the task description. So it would be awesome if they could be included into the properties fold or have their own fold.

    I've found though that if you set the org_log_into_drawer = "LOGBOOK" in the config this is fixed.

  • New: Things that are still broken or not developed.

Habit management

  • New: Select which habits you want to work with.

    Our responses to the cues are so deeply encoded that it may feel like the urge to act comes from nowhere. For this reason, we must begin the process of behavior change with awareness. Before we can effectively build new habits, we need to get a handle on our current ones. The author suggests to do a list of your daily habits and rate them positively, negatively or neutral under the judgement of whether it brings you closer to the desired person you want to be.

    I find this approach expensive time-wise if you already have a huge list of habits to work with. As it's my case I'll skip this part. You can read it in more detail in the chapter "4: The Man Who Didn't Look Right".

  • New: Working with the habit cues.

    The first place to start the habit design is to understand and tweak the triggers that produce them. We'll do it by:

  • New: Clearly formulate the habit you want to change.

    The cues that can trigger an habit can come in a wide range of forms but the two most common are time and location. Being specific about what you want and how you will achieve it helps you say no to things that derail progress, distract your attention and pull you off course. And with enough repetition, you will get the urge to do the right thing at the right time, even if you can't say why. That's why it's interesting to formulate your habits as "I will [behaviour] at [time] in [location]".

    You want the cue to be highly specific and immediately actionable. If there is room for doubt the implementation will suffer. Continuously refine the habit definitions as you catch the exceptions that drift you off.

    If you aren't sure of when to start your habit, try the first day of the week, month or year. People are more likely to take action at those times because hope is usually higher as you get the feeling of a fresh start.

  • New: Habit stacking.

    Many behaviours are linked together where the action of the first is the cue that triggers the next one. You can use this connection to build new habits based on your established ones. This may be called habit stacking. The formulation in this case is "After [current habit], I will [new habit]".

    The key is to tie your desired behaviour into something you already do each day. Once you have mastered this basic structure, you can begin to create larger stacks by chaining small habits together. The catch is that the new habit should have the same frequency as the established one.

    One way to find the right trigger for your habit stack is by brainstorming over:

    • The list of your current habits.
    • A new list of things that always happen to you with that frequency.

    With these two lists, you can begin searching for the best triggers for the stack.

  • New: Use the environment to tweak your cues.

    The cues that trigger a habit can start out very specific, but over time your habits become associated not with a single trigger but with the entire context surrounding the behaviour. This stacks over itself and your habits change depending on the room you are in and the cues in front of you. The context or the environment is then the invisible hand that shapes behaviours. They are not defined by the objects in the environment but by our relationship to them.

    A new environment is a good foundation to make new habits, as you are free from the subtle triggers that nudge you toward your current habits. When you can't manage to get an entirely new environment, you can redefine or rearrange your current one.

    When building good habits you can rearrange the environment to create obvious visual cues that draw your attention towards the desired habit. By sprinkling triggers throughout your surroundings, you increase the odds that you'll think about your habit throughout the day.

    Once a habit has been encoded, the urge to act follows whenever the environmental cues reappear. This is why bad habits reinforce themselves. As you carry through the behaviour you spiral into a situation where the craving keeps growing and points you to keep on going with the same response. For example watching TV makes you feel sluggish, so you watch more television because you don't have the energy to do anything else.

    Even if you manage to break a habit, you are unlikely to forget it's cues even if you don't do it for a while. That means that simply resisting temptation is an ineffective strategy. In the short run it may work. In the long run, as self-control is an exhausting task that consumes willpower, we become a product of the environment we live in. Trying to change a habit with self-control is doomed to fail as you may be able to resist temptation once or twice, but it's unlikely you can muster the willpower to override your desires every time. It's also very hard and frustrating to try to achieve change when you're under the mood influences of a bad habit.

    A more reliable approach is to cut bad habits off at the source. Tweak the environment to make the cue virtually impossible to happen. That way you won't even have the chance to fall for the craving.

  • New: Temptation bundling.

    Dopamine is a neurotransmitter that can be used as the scientific measurement of craving. For years we assumed that it was all about pleasure, but now we know it plays a central role in many neurological processes, including motivation, learning and memory, punishment and aversion and voluntary movement.

    Habits are a dopamine-driven feed back loop. It is released not only when you receive a reward but also when you anticipate it. This anticipation, and not the fulfillment of it, is what gets us to take action.

    If we make a habit more attractive it will release more dopamine which will gives us more motivation to carry it through.

    Temptation bundling works by pairing an action you want to do with an action you need to do. You're more likely to find a behaviour attractive if you get to do one of your favourite things at the same time. In the end you may even look forward to do the habit you need as it's related to the habit you want.

  • New: Align your personal identity change with an existent shared identity.

    We pick up habits from the people around us. As a general rule, the closer we are to someone, the more likely we are to imitate some of their habits. One of the most effective things you can do to build better habits is to join a culture where your desired behaviour is the normal one. This transforms your personal identity transformation into the building of a shared one. Shared identities have great benefits over single ones:

    • They foster belonging. A powerful feeling that creates motivation.
    • They are more resilient: When one falters others will take their place so all together you'll guarantee the maintenance of the identity.
    • They create friendship and community
    • They expose you to an environment where more habits tied to that identity thrive.

    Likewise, if you're trying to run from a bad habit cut your ties to communities that embrace that habit.

Life Chores Management

Route Management

  • New: Introduce route management.

    To analyze which hiking routes are available in a zone I'm following the next process

    • Check in my trips orgmode directory to see if the zone has already been indexed.
    • Do a first search of routes
    • Identify which books or magazines describe the zone
    • For each of the described routes in each of these books:
      • Create the Routes section with tag :route: if it doesn't exist
      • Fill up the route form in a TODO heading. Something similar to:
        Reference: Book Page
        Source: Where does it start
        Distance: X km
        Slope: X m
        Type: [Lineal/Circular/Semi-lineal]
        Track: URL (only if you don't have to search for it)
      • Add tags of the people I'd like to do it with
      • Put a postit on the book/magazine if it's likely I'm going to do it
      • Open a web maps tab with the source of the route to calculate the time from the different lodgins
    • If there are not enough, repeat the process above for each of your online route reference blogs

    • Choose the routes to do

    • Show the gathered routes to the people you want to go with
    • Select which ones you'll be more likely to do

    • For each of the chosen routes

    • Search the track in wikiloc if it's missing
    • Import the track in OsmAnd+



Python Mysql


  • New: Adding a footer to a table.

    Adding a footer is not easy task. This answer doesn't work anymore as table doesn't have the add_footer. You need to create the footer in the add_column so you need to have the data that needs to go to the footer before building the rows.

    You would do something like:

    table = Table(title="Star Wars Movies", show_footer=True)
    table.add_column("Title", style="magenta", footer='2342')



  • New: Introduce neotree.

    General keymaps:

    • <cr>: Open the file in the current buffer
    • <s>: Open in a vertical split
    • <S>: Open in an horizontal split
    • <bs>: Navigate one directory up (even if it's the root of the cwd)

    File and directory management:

    • a: Create a new file or directory. Add a / to the end of the name to make a directory.
    • d: Delete the selected file or directory
    • r: Rename the selected file or directory
    • y: Mark file to be copied (supports visual selection)
    • x: Mark file to be cut (supports visual selection)
    • m: Move the selected file or directory
    • c: Copy the selected file or directory


  • New: Show hidden files.

    return {
      opts = {
        filesystem = {
          filtered_items = {
            visible = true,
            show_hidden_count = true,
            hide_dotfiles = false,
            hide_gitignored = true,
            hide_by_name = {
            never_show = {},
  • New: Autoclose on open file.

    This example uses the file_open event to close the Neo-tree window when a file is opened. This applies to all windows and all sources at once.

      event_handlers = {
          event = "file_opened",
          handler = function(file_path)
            -- auto close
            -- vimc.cmd("Neotree close")
            -- OR
            require("neo-tree.command").execute({ action = "close" })
  • New: Configuring vim folds.

    Copy the code under implementation in your config file.

  • New: Can't copy file/directory to itself.

    If you want to copy a directory you need to assume that the prompt is done from within the directory. So if you want to copy it to a new name at the same level you need to use ../new-name instead of new-name.

  • New: Introduce the vim foldings workflow.

    ne way to easily work with folds is by using the fold-cycle plugin to be able to press <tab> or <enter> to toggle a fold.

    If you're using lazyvim you can use the next configuration:

    return {
        config = function()
        keys = {
              return require("fold-cycle").open()
            desc = "Fold-cycle: open folds",
            silent = true,
              return require("fold-cycle").open()
            desc = "Fold-cycle: open folds",
            silent = true,
              return require("fold-cycle").close()
            desc = "Fold-cycle: close folds",
            silent = true,
              return require("fold-cycle").close_all()
            remap = true,
            silent = true,
            desc = "Fold-cycle: close all folds",


  • New: Introduce LazyVim.

  • New: Adding plugins configuration.

    Configuring LazyVim plugins is exactly the same as using lazy.nvim to build a config from scratch.

    For the full plugin spec documentation please check the lazy.nvim readme.

    LazyVim comes with a list of preconfigured plugins, check them here before diving on your own.

  • New: Adding a plugin.

    Adding a plugin is as simple as adding the plugin spec to one of the files under `lua/plugins/*.lua``. You can create as many files there as you want.

    You can structure your lua/plugins`` folder with a file per plugin, or a separate file containing all the plugin specs for some functionality. For example:lua/plugins/lsp.lua`

    return {
      -- add symbols-outline
        cmd = "SymbolsOutline",
        keys = { { "<leader>cs", "<cmd>SymbolsOutline<cr>", desc = "Symbols Outline" } },
        opts = {
          -- add your options that should be passed to the setup() function here
          position = "right",

    Customizing plugin specs. Defaults merging rules:

    • cmd: the list of commands will be extended with your custom commands
    • event: the list of events will be extended with your custom events
    • ft: the list of filetypes will be extended with your custom filetypes
    • keys: the list of keymaps will be extended with your custom keymaps
    • opts: your custom opts will be merged with the default opts
    • dependencies: the list of dependencies will be extended with your custom dependencies
    • any other property will override the defaults

    For ft, event, keys, cmd and opts you can instead also specify a values function that can make changes to the default values, or return new values to be used instead.

    -- change trouble config
      -- opts will be merged with the parent spec
      opts = { use_diagnostic_signs = true },
    -- add cmp-emoji
      dependencies = { "hrsh7th/cmp-emoji" },
      ---@param opts cmp.ConfigSchema
      opts = function(_, opts)
        table.insert(opts.sources, { name = "emoji" })

    Defining the plugin keymaps:

    Adding keys= follows the rules as explained above. You don't have to specify a mode for normal mode keymaps.

    You can also disable a default keymap by setting it to false. To override a keymap, simply add one with the same lhs and a new rhs. For example lua/plugins/telescope.lua

    return {
      keys = {
        -- disable the keymap to grep files
        {"<leader>/", false},
        -- change a keymap
        { "<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Find Files" },
        -- add a keymap to browse plugin files
          function() require("telescope.builtin").find_files({ cwd = require("lazy.core.config").options.root }) end,
          desc = "Find Plugin File",

    Make sure to use the exact same mode as the keymap you want to disable.

    return {
      keys = {
        -- disable the default flash keymap
        { "s", mode = { "n", "x", "o" }, false },
    You can also return a whole new set of keymaps to be used instead. Or return {} to disable all keymaps for a plugin.

    return {
      -- replace all Telescope keymaps with only one mapping
      keys = function()
        return {
          { "<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Find Files" },
  • New: Auto update plugins.

    Add this to ~/.config/nvim/lua/config/autocomds.lua

    local function augroup(name)
      return vim.api.nvim_create_augroup("lazyvim_" .. name, { clear = true })
    vim.api.nvim_create_autocmd("VimEnter", {
      group = augroup("autoupdate"),
      callback = function()
        if require("lazy.status").has_updates then
          require("lazy").update({ show = false })
  • New: Introduce vim keymaps.

    LazyVim comes with some sane default keybindings, you can see them here. You don't need to remember them all, it also comes with which-key to help you remember your keymaps. Just press any key like and you'll see a popup with all possible keymaps starting with .

    • default <leader> is <space>
    • default <localleader> is \

    General editor bindings:

    • Save file: <C-s>
    • Quit all: <leader>qq
    • Open a floating terminal: <C-/>

    Movement keybindings:

    • Split the windows:
    • Vertically: <C-|
    • Horizontally: <C--
    • Delete window: <leader>wd
    • To move around the windows you can use: , , , .
    • To resize the windows use: , , ,
    • To move between buffers:
    • Next and previous with ,
    • Switch to the previously opened buffer: <leader>bb

    Coding keybindings:


    • <leader>cd>: Shows you the diagnostics message of the current line in a floating window
    • ]d and [d: iterates over all diagnostics
    • ]e and [e: iterates over all error diagnostics
    • ]w and [w: iterates over all warning diagnostics
  • New: Setting keymaps in lua.

    If you need to set keymaps in lua you can use vim.keymap.set. For example:

    vim.keymap.set('n', '<space>w', '<cmd>write<cr>', {desc = 'Save'})

    After executing this, the sequence Space + w will call the write command. Basically, we can save changes made to a file with Space + w.

    Let's dive into what does the vim.keymap.set parameters mean.

    vim.keymap.set({mode}, {lhs}, {rhs}, {opts})
    • {mode}: mode where the keybinding should execute. It can be a list of modes. We need to speify the mode's short name. Here are some of the most common.
    • n: Normal mode.
    • i: Insert mode.
    • x: Visual mode.
    • s: Selection mode.
    • v: Visual + Selection.
    • t: Terminal mode.
    • o: Operator-pending.
    • '': Yes, an empty string. Is the equivalent of n + v + o.

    • {lhs}: is the key we want to bind.

    • {rhs} is the action we want to execute. It can be a string with a command or an expression. You can also provide a lua function.
    • {opts} this must be a lua table. If you don't know what is a "lua table" just think is a way of storing several values in one place. Anyway, it can have these properties.

    • desc: A string that describes what the keybinding does. You can write anything you want.

    • remap: A boolean that determines if our keybinding can be recursive. The default value is false. Recursive keybindings can cause some conflicts if used incorrectly. Don't enable it unless you know what you're doing.
    • buffer: It can be a boolean or a number. If we assign the boolean true it means the keybinding will only be effective in the current file. If we assign a number, it needs to be the "id" of an open buffer.
    • silent: A boolean. Determines whether or not the keybindings can show a message. The default value is false.
    • expr: A boolean. If enabled it gives the chance to use vimscript or lua to calculate the value of {rhs}. The default value is false.
  • New: The leader key.

    When creating keybindings we can use the special sequence <leader> in the {lhs} parameter, it'll take the value of the global variable mapleader.

    So mapleader is a global variable in vimscript that can be string. For example.

    vim.g.mapleader = ' '

    After defining it we can use it as a prefix in our keybindings.

    vim.keymap.set('n', '<leader>w', '<cmd>write<cr>')

    This will make <space key> + w save the current file.

    There are different opinions on what key to use as the <leader> key. The <space> is the most comfortable as it's always close to your thumbs, and it works well with both hands. Nevertheless, you can only use it in normal mode, because in insert <space><whatever> will be triggered as you write. An alternative is to use ; which is also comfortable (if you use the english key distribution) and you can use it in insert mode.

    If you want to define more than one leader key you can either:

    • Change the mapleader many times in your file: As the value of mapleader is used at the moment the mapping is defined, you can indeed change that while plugins are loading. For that, you have to explicitly :runtime the plugins in your ~/.vimrc (and count on the canonical include guard to prevent redefinition later):

    let mapleader = ','
    runtime! plugin/NERD_commenter.vim
    runtime! ...
    let mapleader = '\'
    runime! plugin/mark.vim
    * Use the keys directly instead of using <leader>

    " editing mappings
    nnoremap ,a <something>
    nnoremap ,k <something else>
    nnoremap ,d <and something else>
    " window management mappings
    nnoremap gw <something>
    nnoremap gb <something else>

    Defining mapleader and/or using <leader> may be useful if you change your mind often on what key to use a leader but it won't be of any use if your mappings are stable.

  • New: Configure vim from scratch.

    Neovim configuration is a complex thing to do, both to start and to maintain. The configurations are endless, the plugins are too. Be ready to spend a lot of energy on it and to get lost reading a lot.

    If I'm scaring you, you are right to be scared! xD. Once you manage to get it configured to your liking you'll think that in the end it doesn't even matter spending all that time. However if you're searching for something that is plug and play try vscodium.

    To make things worse, the configuration is done in lua, so you may need a small refreshment to understand what are you doing.

  • New: Vim distributions.

    One way to make vim's configuration more bearable is to use vim distributions. These are projects that maintain configurations with sane defaults and that work with the whole ecosystem of plugins.

    Using them is the best way to:

    • Have something usable fast
    • Minimize the maintenance efforts as others are doing it for you (plugin changes, breaking changes, ..)
    • Keep updated with the neovim ecosystem, as you can see what is the community adding to the default config.

    However, there are so many good Neovim configuration distributions that it becomes difficult for a Neovim user to decide which distribution to use and how to tailor it for their use case.

    By far, the top 5 Neovim configuration distributions are AstroNvim, kickstart, LazyVim, LunarVim, and NvChad. That is not to say these are the “best” configuration distributions, simply that they are the most popular.

    Each of these configuration distributions has value. They all provide excellent starting points for crafting your own custom configuration, they are all extensible and fairly easy to learn, and they all provide an out-of-the-box setup that can be used effectively without modification.

    Distinguishing features of the top Neovim configuration distributions are:

    • AstroNvim:

      • An excellent community repository
      • Fully featured out-of-the-box
      • Good documentation
    • kickstart

      • Minimal out-of-the-box setup
      • Easy to extend and widely used as a starting point
      • A good choice if your goal is hand-crafting your own config
    • LazyVim

      • Very well maintained by the author of lazy.nvim
      • Nice architecture, it’s a plugin with which you can import preconfigured plugins
      • Good documentation
    • LunarVim

      • Well maintained and mature
      • Custom installation processs installs LunarVim in an isolated location
      • Been around a while, large community, widespread presence on the web
    • NvChad

      • Really great base46 plugin enables easy theme/colorscheme management
      • Includes an impressive mappings cheatsheet
      • ui plugin and nvim-colorizer

    Personally I tried LunarVim and finally ended up with LazyVim because:

    • It's more popular
    • I like the file structure
    • It's being maintained by folke one of the best developers of neovim plugins.
  • New: Starting your configuration with LazyVim.

    Installing the requirements:

    LazyVim needs the next tools to be able to work:

    • Neovim >= 0.9.0 (needs to be built with LuaJIT). Follow these instructions
    • Git >= 2.19.0 (for partial clones support). sudo apt-get install git.
    • a Nerd Font(v3.0 or greater) (optional, but strongly suggested as they rae needed to display some icons). Follow these instructions if you're using kitty.
    • lazygit (optional and I didn't like it)
    • a C compiler for nvim-treesitter. apt-get install gcc
    • for telescope.nvim (optional)
    • live grep: ripgrep
    • find files: fd
    • a terminal that support true color and undercurl:
    • kitty (Linux & Macos)
    • wezterm (Linux, Macos & Windows)
    • alacritty (Linux, Macos & Windows)
    • iterm2 (Macos)

    Install the starter:

    • Make a backup of your current Neovim files:
      # required
      mv ~/.config/nvim{,.old}
      # optional but recommended
      mv ~/.local/share/nvim{,.old}
      mv ~/.local/state/nvim{,.old}
      mv ~/.cache/nvim{,.old}
    • Clone the starter

      git clone ~/.config/nvim
    • Remove the .git folder, so you can add it to your own repo later

      rm -rf ~/.config/nvim/.git
    • Start Neovim!

      - It is recommended to run :LazyHealth after installation. This will load all plugins and check if everything is working correctly.

    Understanding the file structure:

    The files under config will be automatically loaded at the appropriate time, so you don't need to require those files manually.

    You can add your custom plugin specs under lua/plugins/. All files there will be automatically loaded by lazy.nvim.

    ├── lua
    │   ├── config
    │   │   ├── autocmds.lua
    │   │   ├── keymaps.lua
    │   │   ├── lazy.lua
    │   │   └── options.lua
    │   └── plugins
    │       ├── spec1.lua
    │       ├── **
    │       └── spec2.lua
    └── init.toml
    The files autocmds.lua, keymaps.lua, lazy.lua and options.lua under lua/config will be automatically loaded at the appropriate time, so you don't need to require those files manually. LazyVim comes with a set of default config files that will be loaded before your own.

    You can continue your config by adding plugins.

  • New: Configure vim to work with markdown.

    Markdown specific plugins:

  • New: Enable folds.

    If you have set the foldmethod to indent by default you won't be able to use folds in markdown.

    To fix this you can create the next autocommand (in lua/config/autocmds.lua if you're using lazyvim).

    vim.api.nvim_create_autocmd("FileType", {
      pattern = "markdown",
      callback = function()
        vim.wo.foldmethod = "expr"
        vim.wo.foldexpr = "v:lua.vim.treesitter.foldexpr()"
  • New: Aligning tables in markdown.

    In the past I used Tabular but it doesn't work with the latest neovim and the project didn't have any update in the last 5 years.

    A good way to achieve this without installing any plugin is to:

    • select the table, including the header and footer lines (with shift V, for example).
    • Prettify the table with :!column -t -s '|' -o '|'

    If you don't want to remember that command you can bind it to a key:

    vim.keymap.set("v", "<leader>tf", "!column -t -s '|' -o '|'<cr>", { desc = "Format table" })

    How the hell this works?

    • shift V switches to Visual mode linewise. This is to select all the lines of the table.
    • : switches to Command line mode, to type commands.
    • ! specifies a filter command. This means we will send data to a command to modify it (or to filter) and replace the original lines. In this case we are in Visual mode, we defined the input text (the selected lines) and we will use an external command to modify the data.
    • column is the filter command we are using, from the util-linux package. column’s purpose is to “columnate”. The -t flag tells column to use the Table mode. The -s flag specifies the delimiters in the input data (the default is whitespace). And the -o flag is to specify the output delimiter to use (we need that because the default is two whitespaces).
  • New: Introduce the vim movement workflow.

    Moving around vim can be done in many ways, which an lead to being lost on how to do it well.

    LazyVim has a very nice way to deal with buffers - Use H and L if the buffer you want to go to is visually close to where you are. - Otherwise, if the buffer is open, use <leader>, - For other files, use <leader><space> - Close buffers you no longer need with <leader>bd - <leader>ss to quickly jump to a function in the buffer you're on - Using the jump list with <c-o>, <c-i> and gd to navigate the code - You can pin buffers with <leader>bp and delete all non pinned buffers with <leader>bP

  • New: Using the jump list.

    Vim has a feature called the “Jump List”, which saves all the locations you’ve recently visited, including their line number, column number, and what else not in the .viminfo file, to help you get exactly the position you were last in. Not only does it save the locations in your current buffer, but also previous buffers you may have edited in other Vim sessions. Which means, if you’re currently working on a file, and there aren’t many last-location saves in this one, you’ll be redirected to the previous file you had edited. But how do you do that? Simply press Ctrl + O, and it’ll get you back to the previous location you were in, or more specifically, your cursor was in.

    If you want to go back to the newer positions, after you’re done with what you wanted to do, you can then press Ctrl + i to go back to the newer position. This is exceptionally useful when you’re working with a lot of project files at a time, and you need to go back and forth between multiple blocks in different files. This could instantly give you a boost, as you won’t need to have separate buffers opened up or windows to be setted up, you can simply jump between the files and edit them.

    Ctrl + O is probably not meant for a single task, as far as Vim’s philosophy is concerned. The jumping mentioned in the previous section only works when you’re in Normal Mode, and not when you’re in Insert Mode. When you press Ctrl + O in Insert Mode, what happens instead is that you’ll enter Normal Mode, and be able to execute a single command, after which Vim will automatically switch back to Insert Mode.



  • New: Introduce Debug Adapter Protocol.

    nvim-dap]( implements a client for the Debug Adapter Protocol. This allows a client to control a debugger over a documented API. That allows us to control the debugger from inside neovim, being able to set breakpoints, evaluate runtime values of variables, and much more.

    nvim-dap is not configured for any language by default. You will need to set up a configuration for each language. For the configurations you will need adapters to run.

    I would suggest starting with 2 actions. Setting breakpoints and “running” the debugger. The debugger allows us to stop execution and look at the current state of the program. Setting breakpoints will allow us to stop execution and see what the current state is.

    vim.api.nvim_set_keymap('n', '<leader>b', [[:lua require"dap".toggle_breakpoint()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<leader>c', [[:lua require"dap".continue()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<leader>n', [[:lua require"dap".step_over()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<leader>N', [[:lua require"dap".step_into()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<F5>', [[:lua require"osv".launch({port = 8086})<CR>]], { noremap = true })

    Go to a line where a conditional or value is set and toggle a breakpoint. Then, we’ll start the debugger. If done correctly, you’ll see an arrow next to your line of code you set a breakpoint at.

    There is no UI with dap by default. You have a few options for UI nvim-dap-ui

    In the dap repl you can use the next operations:

    • .exit: Closes the REPL
    • .c or .continue: Same as |dap.continue|
    • .n or .next: Same as |dap.step_over|
    • .into: Same as |dap.step_into|
    • .into_target: Same as |dap.step_into{askForTargets=true}|
    • .out: Same as |dap.step_out|
    • .up: Same as |dap.up|
    • .down: Same as |dap.down|
    • .goto: Same as |dap.goto_|
    • .scopes: Prints the variables in the current scopes
    • .threads: Prints all threads
    • .frames: Print the stack frames
    • .capabilities: Print the capabilities of the debug adapter
    • .b or .back: Same as |dap.step_back|
    • .rc or .reverse-continue: Same as |dap.reverse_continue|
  • New: Introduce nvim-dap-ui.

    Install with packer:

    use { "rcarriga/nvim-dap-ui", requires = {"mfussenegger/nvim-dap"} }

    It is highly recommended to use neodev.nvim to enable type checking for nvim-dap-ui to get type checking, documentation and autocompletion for all API functions.

      library = { plugins = { "nvim-dap-ui" }, types = true },
    nvim-dap-ui is built on the idea of "elements". These elements are windows which provide different features.

    Elements are grouped into layouts which can be placed on any side of the screen. There can be any number of layouts, containing whichever elements desired.

    Elements can also be displayed temporarily in a floating window.

    Each element has a set of mappings for element-specific possible actions, detailed below for each element. The total set of actions/mappings and their default shortcuts are:

    • edit: e
    • expand: <CR> or left click
    • open: o
    • remove: d
    • repl: r
    • toggle: t

    See :h dapui.setup() for configuration options and defaults.

    To get started simply call the setup method on startup, optionally providing custom settings.


    You can open, close and toggle the windows with corresponding functions:

  • New: Debug neovim plugins with DAP.

    one-small-step-for-vimkind is an adapter for the Neovim lua language. It allows you to debug any lua code running in a Neovim instance.

    Install it with Packer:

    use 'mfussenegger/nvim-dap'
    After installing one-small-step-for-vimkind, you will also need a DAP plugin which will allow you to interact with the adapter. Check the install instructions here.

    Then add these lines to your config:

    local dap = require"dap"
    dap.configurations.lua = {
        type = 'nlua',
        request = 'attach',
        name = "Attach to running Neovim instance",
    dap.adapters.nlua = function(callback, config)
      callback({ type = 'server', host = or "", port = config.port or 8086 })

Neovim Plugin Development

  • New: Debugging with DAP.

    You can debug Lua code running in a separate Neovim instance with jbyuki/one-small-step-for-vimkind.

    The plugin uses the Debug Adapter Protocol. Connecting to a debug adapter requires a DAP client like mfussenegger/nvim-dap. Check how to configure here

    Once you have all set up and assuming you're using the next keybindings for nvim-dap:

    vim.api.nvim_set_keymap('n', '<leader>b', [[:lua require"dap".toggle_breakpoint()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<leader>c', [[:lua require"dap".continue()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<leader>n', [[:lua require"dap".step_over()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<leader>N', [[:lua require"dap".step_into()<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<F5>', [[:lua require"osv".launch({port = 8086})<CR>]], { noremap = true })
    vim.api.nvim_set_keymap('n', '<leader>B', [[:lua require"dapui".toggle()<CR>]], { noremap = true })

    You will debug the plugin by:

    • Launch the server in the debuggee using F5.
    • Open another Neovim instance with the source file (the debugger).
    • Place breakpoint with <leader>b.
    • On the debugger connect to the DAP client with <leader>c.
    • Optionally open the nvim-dap-ui with <leader>B in the debugger.
    • Run your script/plugin in the debuggee
    • Interact in the debugger using <leader>n to step to the next step, and <leader>N to step into. Then use the dap console to inspect and change the values of the state.


  • New: Introduce immich.

    Self-hosted photo and video backup solution directly from your mobile phone.


  • New: Installation.

    • Create a directory of your choice (e.g. ./immich-app) to hold the docker-compose.yml and .env files.
    mkdir ./immich-app
    cd ./immich-app
    • Download docker-compose.yml, example.env and optionally the hwaccel.yml files:

    wget -O docker-compose.yaml
    wget -O .env
    - Tweak those files with these thoughts in mind: - immich won't respect your upload media directory structure, so until you trust the softwar copy your media to the uploads directory. - immich is not stable so you need to disable the upgrade from watchtower. The easiest way is to pin the latest stable version in the .env file. - Populate custom database information if necessary. - Populate UPLOAD_LOCATION with your preferred location for storing backup assets. - Consider changing DB_PASSWORD to something randomly generated

    • From the directory you created in Step 1, (which should now contain your customized docker-compose.yml and .env files) run:
    docker compose up -d
  • New: Configure smart search for other language.

    You can change to a multilingual model listed here by going to Administration > Machine Learning Settings > Smart Search and replacing the name of the model.

    Choose the one that has more downloads. For example, if you'd want the +immich-app/XLM-Roberta-Large-Vit-B-16Plus model, you should only enter XLM-Roberta-Large-Vit-B-16Plus in the program configuration. Be careful not to add trailing whitespaces.

    Be sure to re-run Smart Search on all assets after this change. You can then search in over 100 languages.

  • New: External storage.

    If you have an already existing library somewhere immich is installed you can use an external library. Immich will respect the files on that directory.

    It won't create albums from the directory structure. If you want to do that check this or this solutions.

  • New: My personal workflow.

    I've tailored a personal workflow given the next thoughts:

    • I don't want to expose Immich to the world, at least until it's a stable product.
    • I already have in place a sync mechanism with syncthing for all the mobile stuff
    • I do want to still be able to share some albums with my friends and family.
    • I want some mobile directories to be cleaned after importing the data (for example the camera/DCIM), but others should leave the files as they are after the import (OsmAnd+ notes).

    Ingesting the files:

    As all the files I want to ingest are sent to the server through syncthing, I've created a cron script that copies or moves the required files. Something like:

    echo 'Updating the OsmAnd+ data'
    rsync -auhvEX --progress /data/apps/syncthing/data/Osmand/avnotes /data/media/pictures/unclassified
    echo 'Updating the Camera data'
    mv /data/apps/syncthing/data/camera/Camera/* /data/media/pictures/unclassified/
    echo 'Cleaning laptop home'
    mv /data/media/downloads/*jpeg /data/media/downloads/*jpg /data/media/downloads/*png /data/media/pictures/unclassified/

    Where :

    • /data/media/pictures/unclassified is a subpath of my external library.
    • The last echo makes sure that the program exits with a return code of 0. The script is improbable as it only takes into account the happy path, and I'll silently miss errors on it's execution. But as a first iteration it will do the job.

    Then run the script in a cron and log the output to journald:

    0 0 * * * /bin/bash /usr/local/bin/ | /usr/bin/logger -t archive_fotos

    Make sure to configure the update library cron job to run after this script has ended.

  • New: Not there yet.

    There are some features that are still lacking:

  • New: Edit an image metadata.

    You can't do it directly through the interface yet, use exiftool instead.

    This is interesting to remove the geolocation of the images that are not yours

  • New: Do comparison of selfhosted photo software.

    There are many alternatives to self host a photo management software, here goes my personal comparison. You should complement this article with meichthys one.

    TL;DR: I'd first go with Immich, then LibrePhotos and then LycheeOrg

    Software Home-Gallery Immich LibrePhotos
    UI Fine Good Fine
    Popular (stars) 614 25k 6k
    Active (PR/Issues)(1) ? 251/231 27/16
    Easy deployment ? True Complicated
    Good docs True True True
    Stable True False True
    Smart search ? True True
    Language Javascript Typescript Python
    Batch edit True True ?
    Multi-user False True ?
    Mobile app ? True ?
    Oauth support ? True ?
    Facial recognition ? True ?
    Scales well False True ?
    Favourites ? True ?
    Archive ? True ?
    Has API True True ?
    Map support True True ?
    Video Support True True ?
    Discover similar True True ?
    Static site True False ?
    • (1): It refers to the repository stats of the last month



    Pros: - Smart search is awesome Oo - create shared albums that people can use to upload and download - map with leaflet - explore by people and places - docker compose - optional hardware acceleration - very popular 25k stars, 1.1k forks - has a CLI - can load data from a directory - It has an android app on fdroid to automatically upload media - sharing libraries with other users and with the public - favorites and archive - public sharing - oauth2, specially with authentik <3 - extensive api: - It has an UI similar to google photos, so it would be easy for non technical users to use. - Batch edit - Discover similar through the smart search


    • If you want to get results outside the smart search you are going to have a bad time. There is still no way to filter the smart search results or even sort them. You're sold to the AI.
    • dev suggests not to use watchtower as the project is in unstable alpha
    • Doesn't work well in firefox
    • It doesn't work with tags which you don't need because the smart search is so powerful.
    • Scans pictures on the file system




    • docker compose, although you need to build the dockers yourself
    • android app
    • 6k stars, 267 forks
    • object, scene ai extraction


    • Not as good as Immich.


    You can see the demo here.

    Nice features:

    • Simple UI
    • Discover similar images
    • Static site generator
    • Shift click to do batch editing


    • All users see all media
    • The whole database is loaded into the browser and requires recent (mobile) devices and internet connection
    • Current tested limits are about 400,000 images/videos




    • Sharing like it should be. One click and every photo and album is ready for the public. You can also protect albums with passwords if you want. It's under your control.
    • Manual tags
    • apparently safe upgrades
    • docker compose
    • 2.9k stars

    Cons: - demo doesn't show many features - no ai



    • Syncs with file system
    • Albums and individual photos or videos can easily be shared by generating a public or password protected link.
    • users support
    • maps support
    • 4.4k stars
    • Face recognition


    • Demo difficult to understand as it's not in english
    • mobile app only for ios
    • last commit 6 months ago




    • map
    • The gallery also supports *.gpx file to show your tracked path on the map too
    • App supports full boolean logic with negation and exact or wildcard search. It also provides handy suggestions with autocomplete.
    • face recognitiom: PiGallery2 can read face reagions from photo metadata. Current limitation: No ML-based, automatic face detection.
    • rating and grouping by rating
    • easy query builder
    • video transcoding
    • blog support. Markdown based blogging support

    You can write some note in the *.md files for every directory

    • You can create logical albums (a.k.a.: Saved search) from any search query. Current limitation: It is not possible to create albums from a manually picked photos.
    • PiGallery2 has a rich settings page where you can easily set up the gallery.

    Cons: - no ml face recognition



    Piwigo is open source photo management software. Manage, organize and share your photo easily on the web. Designed for organisations, teams and individuals


    • Thousands of organizations and millions of individuals love using Piwigo
    • shines when it comes to classifying thousands or even hundreds of thousands of photos.
    • Born in 2002, Piwigo has been supporting its users for more than 21 years. Always evolving!
    • You can add photos with the web form, any FTP client ora desktop application like digiKam, Shotwell, Lightroom ormobile applications.
    • Filter photos from your collection, make a selection and apply actions in batch: change the author, add some tags, associate to a new album, set geolocation...
    • Make your photos private and decide who can see each of them. You can set permissions on albums and photos, for groups or individual users.
    • Piwigo can read GPS latitude and longitude from embedded metadata. Then, with plugin for Google Maps or OpenStreetMap, Piwigo can display your photos on an interactive map.
    • Change appearance with themes. Add features with plugins. Extensions require just a few clicks to get installed. 350 extensions available, and growing!
    • With the Fotorama plugin, or specific themes such as Bootstrap Darkroom, you can experience the full screen slideshow.
    • Your visitors can post comments, give ratings, mark photos as favorite, perform searches and get notified of news by email.
    • Piwigo web API makes it easy for developers to perform actions from other applications
    • GNU General Public License, or GPL
    • 2.9 k stars, 400 forks
    • still active
    • nice release documents:



    Fast server-based photo management system for large collections of images. Includes face detection, face & object recognition, powerful search, and EXIF Keyword tagging. Runs on Linux, MacOS and Windows.

    Very ugly UI


    Too simple


    Low number of maintainers Too simple

Infrastructure as Code

Ansible Snippets

Infrastructure Solutions


  • New: Troubleshoot don't have enough permissions to start restore from backup.

    That may be because the default EFS backup policy doesn't let you do that (stupid, I know).

    To fix it go into the backup policy and remove the next line from the Deny policy:


Continuous Integration


  • New: Ignore certain files.

    It is possible to exclude specific files or directories, so that the linter doesn’t process them. They can be provided either as a list of paths, or as a bulk string.

    You can either totally ignore files (they won’t be looked at):

    extends: default
    ignore: |
      - /this/specific/file.yaml
      - all/this/directory/
      - '*.template.yaml'

Automating Processes


  • New: Manually renew a certificate.

    Linuxserver swag container renews the certificates at night. If you don't have your server up at those hours your certificate won't be renewed automatically and you need to react to the prometheus alert manually. To do so get into the container and run certbot renew.



  • New: Prevent the too many outstanding requests error.

    Add to your loki config the next options

      split_queries_by_interval: 24h
      max_query_parallelism: 100
      max_outstanding_requests_per_tenant: 4096
      max_outstanding_per_tenant: 4096


  • New: Pipeline building.

    In this issue there are nice examples on different pipelines.

  • New: Drop logs.

    If you don't want the logs that have the keyword systemd-journal and value docker-compose you can add the next pipeline stage:

      - drop:
          source: syslog_identifier
          value: docker-compose

Operating Systems


Linux Snippets


  • New: Suggest to use nerd fonts.

    If you're thinking of adding a new font, you should take a look at the nerd fonts as they patch developer targeted fonts with a high number of glyphs (icons). Specifically to add a high number of extra glyphs from popular ‘iconic fonts’ such as Font Awesome, Devicons, Octicons, and others.

    For example you can get the FiraCode Nerd Font


Artificial Intelligence

Coding by Voice

  • New: Introduce Coding by voice.

    Coding by voice command requires two kinds of software: a speech-recognition engine and a platform for voice coding. Dragon from Nuance, a speech-recognition software developer in Burlington, Massachusetts, is an advanced engine and is widely used for programming by voice. On the platform side, VoiceCode by Ben Meyer and Talon by Ryan Hileman (both are for Mac OS only) are popular.

    Coding by voice platforms:

    Two platforms for voice programming are Caster and Aenea, the latter of which runs on Linux. Both are free and open source, and enable voice-programming functionality in Dragonfly, which is an open-source Python framework that links actions with voice commands detected by a speech-recognition engine. Saphra tried Dragonfly, but found that setup required more use of her hands than she could tolerate.

    All of these platforms for voice command work independently of coding language and text editor, and so can also be used for tasks outside programming. Pimentel, for instance, uses voice recognition to write e-mails, which he finds easier, faster and more natural than typing.

    To the untrained ear, coding by voice command sounds like staccato bursts of a secret language. Rudd’s video is full of terms like ‘slap’ (hit return), ‘sup’ (search up) and ‘mara’ (mark paragraph).

    Unlike virtual personal assistants such as Apple’s Siri or Google’s Alexa, VoiceCode and Talon don’t do natural-language processing, so spoken instructions have to precisely match the commands that the system already knows. But both platforms use continuous command recognition, so users needn’t pause between commands, as Siri and Alexa require.

    VoiceCode commands typically use words not in the English language, because if you use an English word as a command, such as ‘return’, it means you can never type out that word. By contrast, Talon, Aenea and Caster feature dynamic grammar, a tool that constantly updates which words the software can recognize on the basis of which applications are open. This means users can give English words as commands without causing confusion.

    In addition to voice recognition, Talon can also replace a computer mouse with eye tracking, which requires a Tobii 4c eye tracker (US$150). Other eye-mousing systems generally require both the eye tracker and head-tracking hardware, such as the TrackIR from NaturalPoint. “I want to make fully hands-free use of every part of a desktop computer a thing,” says Hileman. Other mouse replacements also exist; Pimentel uses one called SmartNav.

    Voice command requires at least a decent headset or microphone. Many users choose a unidirectional microphone so that others can talk to them while they are dictating code. One such mic, a cardioid mic, requires special equipment to supply power, and hardware costs can reach $400, says Pimentel.

    The software can cost several hundred dollars too. The speech-recognition engine Dragon Professional costs $300, as does VoiceCode. Caster and Aenea are free and open source. Talon is available for free, but requires a separate speech-recognition engine. A beta version of Talon that includes a built-in speech-recognition engine is currently available to Hileman’s Patreon supporters for $15 per month. 

    Whether or not users have RSI, it can be difficult and frustrating to start programming by voice. It took a month and a half for Pimentel to get up to speed, he says, and there were days when he was ready to throw in the towel. He printed out 40 pages of commands and forced himself to look at them until he learnt them. Saphra needed two months of coding, a little every day, before she felt that it was a “perfectly enjoyable experience and I could see myself doing this for a living”.

    After the initial learning curve, users often create custom prompts for commonly used commands as the need arises.