Note Taking with Neovim and Obsidian - Updated!

8 minute read Published: 2025-08-07

Hello. In this post I am going through how I take notes, including math, in markdown using Obsidian and Neovim. I will mostly be focusing on the configuration, not the workflow.

Table of Contents

Foreword

My solution is not perfect, far from it. It was made over some period of time and I probably forgot something important in the meantime. I am primarily writing this guide for myself to remember what I did, but I am putting it online in case it saves at least a minute of someone's life.

If your goal is to study math, simply take a notebook or buy an iPad and learn that way. I have a horrible problem with procrastinating and this sits as a reminder of all the exams I failed because I was playing around in Neovim and Obsidian. It got to the point where I started writing plugins, for obsidian plugins. Which, of course, I then abandoned because the point wasn't to make my life easier, but to lessen the discomfort of sitting and doing something I didn't momentarily enjoy -- math.

I updated this guide to include blink.cmp and the community fork of obsidian.nvim.

Everything here should serve as a reference, it was mostly generated by AI and then tested by me.

With that in mind let's begin!

Requirements

This setup is designed for Linux(Hyprland) using:

  • Neovim >= 0.8
  • Obsidian
  • Kitty terminal
  • wl-copy, wtype, etc.

Neovim

I assume you know how to set up Neovim and already have a configuration. If you just want my configuration as a reference you can find it here. I am just going to go through some main plugins that are markdown and Obsidian related, not specific like treesitter or bufferline :).

Worth noting, I don't use Neovim for previewing and navigating files. I think it is more convenient and prettier in Obsidian. Neovim serves purely as a text writing tool.

Note Before the Plugins

Lazy.nvim

I use lazy.nvim to manage all of my plugins. If you use something else adjust accordingly.

Workspace

If you are interested this is how my workspace looks like when I am writing: Example

As you can see Obsidian is still a big part of the workspace. If that's something that's bothering you, I recommend you to read the rest of the Neovim section and keep in mind that you will have to configure obsidian.nvim much more than I did.

Snippets

Snippets are a big part of this setup. Especially for LaTeX math. You can live without markdown snippets in Neovim, as Obsidian has some but then you will have to click with a mouse in Obsidian to insert text. Which we don't want.

Luasnip Configuration

LuaSnip is a great plugin for snippets. The configuration below also includes friendly snippets, which are very convenient for editing markdown.

return {
  'L3MON4D3/LuaSnip',
  dependencies = {
    'rafamadriz/friendly-snippets',
  },
  version = 'v2.*', -- Recommended for Neovim >= 0.8
  build = 'make install_jsregexp', -- Needed for JS/TS regex support
  opts = {
    enable_autosnippets = true,
    store_selection_keys = '<Tab>',
    -- Other default luasnip options can go here
  },
  config = function(_, opts)
    require('luasnip').setup(opts)
    require('luasnip.loaders.from_vscode').lazy_load()
    vim.keymap.set({ 'i', 's' }, '<c-l>', function()
      require('luasnip').expand_or_jump()
    end, { silent = true })
  end,
}

jsregexp is a must as the next plugin uses it, as well as auto snippets. I use <C-l> for expanding snippets and jumping but that's mostly just preference.

I was really happy when I discovered that blink.cmp works much better with LuaSnip now than it did before! Blink.cmp is faster, replaces like four other plugins and it is the standard in most setups. This is my configuration. It is mostly a copy of the kickstart.nvim repo.

return {
  'saghen/blink.cmp',
  event = 'VimEnter',
  version = '1.*',
  dependencies = {
    'L3MON4D3/LuaSnip',
    'folke/lazydev.nvim',
  },
  --- @module 'blink.cmp'
  --- @type blink.cmp.Config
  opts = {
    keymap = {
      preset = 'default',
    },

    appearance = {
      nerd_font_variant = 'mono',
    },

    completion = {
      documentation = { auto_show = false, auto_show_delay_ms = 500 },
    },

    sources = {
      default = { 'lsp', 'path', 'snippets', 'lazydev' },
      providers = {
        lazydev = { module = 'lazydev.integrations.blink', score_offset = 100 },
      },
    },

    snippets = { preset = 'luasnip' },

    -- Blink.cmp includes an optional, recommended rust fuzzy matcher,
    -- which automatically downloads a prebuilt binary when enabled.
    --
    -- By default, we use the Lua implementation instead, but you may enable
    -- the rust implementation via `'prefer_rust_with_warning'`
    --
    -- See :h blink-cmp-config-fuzzy for more information
    fuzzy = { implementation = 'lua' },

    signature = { enabled = true },
  },
}

luasnip-markdown-snippets.nvim

Information about the plugin

So this one is my plugin and the documentation for the plugin is just as bad as the name. It is deceptive and sometimes outright wrong. Don't ask me why. Feel free to raise an issue if there is some false information, but I don't offer technical support. With that in mind, this plugin offers a lot of snippets, believe me when I say that I feel my blood pressure rising when i have to write math without this plugin. This plugin is a fork of an existing LuaSnip snippet plugin, but I made it so that it offers markdown support out of the box using treesitter and some regex.

Here is an example of some snippets: SnippetExample

Crucial: this plugin only shows and activates snippets when inside a markdown math block. Display or inline. It also doesn't activate inside a LaTeX text block.

Configuration

Here is my configuration:

return {
  'LukasKorotaj/luasnip-markdown-snippets.nvim',
  config = function()
    local ls = require 'luasnip'
    local t = ls.text_node
    local i = ls.insert_node
    local fmta = require('luasnip.extras.fmt').fmta
    local autosnippet = ls.extend_decorator.apply(ls.snippet, { snippetType = 'autosnippet' })
    local lms = require 'luasnip-markdown-snippets'

    require('luasnip-markdown-snippets').setup {
      snippets = {
        markdown = {
          -- Override existing snippet
          autosnippet(
            { trig = 'cb', wordTrig = false },
            { t '^3' },
            { condition = lms.in_math }
          ),

          autosnippet({ trig = '...', wordTrig = false }, { t '\\ldots' }, { condition = lms.in_math }),
          autosnippet({ trig = ' , ', wordTrig = false }, { t '\\qquad' }, { condition = lms.in_math }),
          autosnippet({ trig = '  ', wordTrig = false }, { t '\\quad' }, { condition = lms.in_math }),
        },
      },
    }
  end,
}

Look at the configuration docs if you want to know more. I really wanted it to be simple to add and override snippets as sometimes you need to change something. Since you will be using this plugin to write a lot of math i suggest you go through the docs, however outdated they might be.

obsidian.nvim

obsidian.nvim in this configuration is primarily focused on linking inside Neovim and making sure that the currently open buffer is the one showed in Obsidian. Honestly, for my use-case, this plugin has too many features. But you may see it as an advantage.
Please read the docs available on the link above as I am not using all of the features. In fact, I disabled most of them.
I am using the community fork here as it is actively maintained and supports blink.cmp.

return {
  'obsidian-nvim/obsidian.nvim',
  version = '*', -- recommended, use latest release instead of latest commit
  lazy = true,
  ft = 'markdown',
  -- Replace the above line with this if you only want to load obsidian.nvim for markdown files in your vault:
  -- event = {
  --   -- If you want to use the home shortcut '~' here you need to call 'vim.fn.expand'.
  --   -- E.g. "BufReadPre " .. vim.fn.expand "~" .. "/my-vault/*.md"
  --   -- refer to `:h file-pattern` for more examples
  --   "BufReadPre path/to/my-vault/*.md",
  --   "BufNewFile path/to/my-vault/*.md",
  -- },
  dependencies = {
    -- Required.
    'nvim-lua/plenary.nvim',
  },
  opts = {
    workspaces = {
      {
        name = 'telekasten',
        path = '/home/shared/telekasten/',
      },
    },
    completion = {
      nvim_cmp = false,
      blink = true,
      min_chars = 2,
      create_new = false,
    },
    new_notes_location = 'current_dir',
    disable_frontmatter = true,
    legacy_commands = false,
  },
}

Of course, you should change the workspace.name and workspace.path. Again, read through all of the options!

Obsidian

So Obsidian previews all the files we edited with Neovim. The reason I chose obsidian is because it is really easy to use and has amazing community plugins. A problem with obsidian is that you will always find more to add. Here are the plugins and their respective settings.

Theme

Not really a plugin but worth noting. Change the theme, makes everything easier to see.

Edit in Neovim

Nice plugin, automatically open the current note in the a new buffer. Set the key-bind for opening Neovim, the close Neovim one doesn't work, at least for me. Also set your terminal in the settings.

If you are trying to edit files in Neovide, it won't work. There is a hard-coded -e flag after the terminal so executing Neovide, even with a set path to the binary, doesn't work. Because the terminal will launch Neovide and quit right away.

Reading View j/k Scroll

My plugin 💪. Which you could tell by the original name. Does what the name says, as well as using gg to go all the way up and G for all the way down. I made it as I use hyprland and hate using the mouse for scrolling. The plugin has some speed settings for preference.

Excalidraw

Excalidraw is one of those main, big plugins. It is used as drawing math can be quite difficult in text. What I did with it is I set a keybinding for opening Excalidraw in a pop-out window in the plugin settings. As well as enabling an automatic .svg copy, which I can embed and use on this site. This is the best solution I found considering the learning curve, functionality and appearance.

Math in Excalidraw

If you are like me you cannot stand writing anything outside of Neovim. And especially math, now that you have your precious snippets :). So I created a bash script that is tested on hyprland, but with some tweaking I think you can get it working on other sessions as well.

#!/usr/bin/env bash

# Script: textGod.sh
# Description: Opens a floating Neovim window in Hyprland for fast math snippet editing.
# - Saves to a temp markdown file
# - Uses Neovim RPC to write/quit and copy to clipboard
# - Automatically pastes into Excalidraw via wtype

CLASS_NAME="textGod"
SOCKET_FILE="/tmp/nvimpipe-textGod"

copy_to_clipboard() {
    if [ -f "$1" ]; then
        if command -v wl-copy &>/dev/null; then
            cat "$1" | wl-copy
        elif command -v xclip &>/dev/null; then
            cat "$1" | xclip -selection clipboard
        fi
    fi
}

# Function to check if window exists
window_exists() {
    hyprctl clients | grep -q "class: $CLASS_NAME"
}

if window_exists; then
    # Window open -> save and quit Neovim via RPC
    if [ -S "$SOCKET_FILE" ]; then
        nvim --server "$SOCKET_FILE" --remote-send '<C-\><C-N>:w<CR>:q<CR>'
        sleep 0.3
        LAST_FILE=$(ls -t /tmp/textGod-*.md 2>/dev/null | head -n 1)
        copy_to_clipboard "$LAST_FILE"
        
        wtype $(wl-paste)
    fi
else
    TMP_FILE=$(mktemp --suffix=.md /tmp/textGod-XXXXXX)
    
    # Start inotify watcher
    inotifywait -mq -e close_write "$TMP_FILE" &
    WATCH_PID=$!

    # Start watchdog to kill watcher if window closes
    (
        while true; do
            sleep 0.5
            if ! window_exists; then
                kill "$WATCH_PID" 2>/dev/null
                break
            fi
        done
    ) &

    WATCHDOG_PID=$!

    kitty --class "$CLASS_NAME" nvim --listen "$SOCKET_FILE" "+startinsert" "$TMP_FILE"

    kill "$WATCH_PID" "$WATCHDOG_PID" 2>/dev/null
    rm -f "$SOCKET_FILE"
fi

This script opens a small Neovim window with the markdown file-type which is saved in /tmp. The window is automatically in insert mode. It then listens to changes. If the script is run again it sends the :wq command, copies the contents of the file and types the contents using wtype, in my case. To cancel it you just quit the window without pressing the key-bind again.

Here is a small gif that show how the window opens. I tried. Gif

There is an issue with this. In order for the snippets to work you need to be inside a math $$ block. But the $ sign confuses Excalidraw, so you have to remove them before running the script again.

So in practice, what you do is start a LaTeX formula in Excalidraw, run the script. Write the math inside a math block, delete $s, start the script again and the text is inserted into the Obsidian window.

Hyprland

I know there are a lot of hyprland specific stuff but I hope that, in worst case, you can take this script as an inspiration to create your own version.

The script needs some configuration in hyprland.conf;

bind = $mainMod SHIFT, T, exec, .local/bin/textGod.sh

windowrule = float, class:^(textGod)$
windowrule = size 800 500, class:^(textGod)$
windowrule = center, class:^(textGod)$

TL;DR

A hacky way to force-edit text in obsidian is to create a bash script that opens a small neovim window, copies the content and inserts it automatically, via literal key input simulation (on hyprland).

Final Thoughts

This system is not perfect, but it works for me. If you love markdown, math, and automation -- maybe it’ll work for you too.

Feel free to open an issue on any of my plugins if something is broken, but no promises on fixes.

Thanks for reading.