From d12f65c200632e139a29b93a3957c244a00e9dc4 Mon Sep 17 00:00:00 2001 From: etrobert-bot Date: Thu, 25 Jun 2026 18:31:33 +0200 Subject: [PATCH 1/5] feat(neovim): add vim-tidal plugin for TidalCycles live coding Packages vim-tidal (not in nixpkgs) from GitHub and wires it into the neovim-wrapped plugin set. Sets g:tidal_target = "tmux" so pattern lines are pasted into a tmux pane running GHCi+Tidal. The pattern language and audio engine come from a project's nix devShell (see the electronic-music TidalCycles flake), not from the editor; this plugin only ships lines to a running GHCi. Co-Authored-By: Claude Opus 4.8 --- pkgs/neovim-wrapped/default.nix | 1 + pkgs/neovim-wrapped/plugins/tidal/default.nix | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 pkgs/neovim-wrapped/plugins/tidal/default.nix diff --git a/pkgs/neovim-wrapped/default.nix b/pkgs/neovim-wrapped/default.nix index a15c2e2e..e9e0a468 100644 --- a/pkgs/neovim-wrapped/default.nix +++ b/pkgs/neovim-wrapped/default.nix @@ -19,6 +19,7 @@ let ./plugins/catppuccin ./plugins/treesj ./plugins/vim-tmux-navigator + ./plugins/tidal ./plugins/which-key ./plugins/conform ./plugins/nvim-lint diff --git a/pkgs/neovim-wrapped/plugins/tidal/default.nix b/pkgs/neovim-wrapped/plugins/tidal/default.nix new file mode 100644 index 00000000..5a051120 --- /dev/null +++ b/pkgs/neovim-wrapped/plugins/tidal/default.nix @@ -0,0 +1,35 @@ +{ pkgs, ... }: +let + # vim-tidal is not in nixpkgs, so package it directly. Pinned to the latest + # commit (the v1.x tags are old); it's a stable vimscript plugin. + vim-tidal = pkgs.vimUtils.buildVimPlugin { + pname = "vim-tidal"; + version = "unstable-2023-06-02"; + + src = pkgs.fetchFromGitHub { + owner = "tidalcycles"; + repo = "vim-tidal"; + rev = "e440fe5bdfe07f805e21e6872099685d38e8b761"; + sha256 = "102j93zygjkrxgdxcsv4nqhnrfn1cbf4djrxlx5sly0bnvbs837j"; + }; + }; +in +{ + plugins = [ + { + plugin = vim-tidal; + config = /* lua */ '' + -- Live-code TidalCycles from .tidal files. The pattern language + -- (GHCi + tidal) and the audio engine (SuperDirt) come from the + -- project's `nix develop` (see the electronic-music flake), not from + -- here -- this plugin only ships lines to a running GHCi. + -- + -- tmux target: -send pastes into a tmux pane running + -- `tidal` (GHCi booted with Tidal). In tmux mode vim-tidal does not + -- start GHCi itself, so launch `tidal` in its own pane first. + vim.g.tidal_target = "tmux" + ''; + extraPackages = with pkgs; [ tmux ]; + } + ]; +} From eb14201c5746f886a2e0cf3ccc7bd405eecc8f25 Mon Sep 17 00:00:00 2001 From: etrobert-bot Date: Fri, 26 Jun 2026 12:33:49 +0200 Subject: [PATCH 2/5] refactor(neovim): adopt nixpkgs version/hash conventions, trim tidal comment Address review on the tidal plugin: - Use SRI `hash = "sha256-..."` instead of base32 `sha256` (algorithm-explicit, matches the existing cmp third-party fetch). - Use the modern nixpkgs `-unstable-` version scheme for both unpinned GitHub fetches: vim-tidal -> 1.4.8-unstable-2023-06-02, and migrate tailwindcss-colorizer-cmp-nvim from the bare-date 2024-01-01 (also wrong: the pinned rev is dated 2024-03-23) to 0-unstable-2024-03-23. - Trim the verbose config comment. Co-Authored-By: Claude Opus 4.8 --- pkgs/neovim-wrapped/plugins/cmp/default.nix | 2 +- pkgs/neovim-wrapped/plugins/tidal/default.nix | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/pkgs/neovim-wrapped/plugins/cmp/default.nix b/pkgs/neovim-wrapped/plugins/cmp/default.nix index faf335f7..767da3c0 100644 --- a/pkgs/neovim-wrapped/plugins/cmp/default.nix +++ b/pkgs/neovim-wrapped/plugins/cmp/default.nix @@ -8,7 +8,7 @@ let tailwindcss-colorizer-cmp-nvim = pkgs.vimUtils.buildVimPlugin { pname = "tailwindcss-colorizer-cmp-nvim"; - version = "2024-01-01"; + version = "0-unstable-2024-03-23"; src = pkgs.fetchFromGitHub { owner = "roobert"; repo = "tailwindcss-colorizer-cmp.nvim"; diff --git a/pkgs/neovim-wrapped/plugins/tidal/default.nix b/pkgs/neovim-wrapped/plugins/tidal/default.nix index 5a051120..7196dbbe 100644 --- a/pkgs/neovim-wrapped/plugins/tidal/default.nix +++ b/pkgs/neovim-wrapped/plugins/tidal/default.nix @@ -4,13 +4,13 @@ let # commit (the v1.x tags are old); it's a stable vimscript plugin. vim-tidal = pkgs.vimUtils.buildVimPlugin { pname = "vim-tidal"; - version = "unstable-2023-06-02"; + version = "1.4.8-unstable-2023-06-02"; src = pkgs.fetchFromGitHub { owner = "tidalcycles"; repo = "vim-tidal"; rev = "e440fe5bdfe07f805e21e6872099685d38e8b761"; - sha256 = "102j93zygjkrxgdxcsv4nqhnrfn1cbf4djrxlx5sly0bnvbs837j"; + hash = "sha256-8gyk17YLeKpLpz3LRtxiwbpsIbZka9bb63nK5/9IUoA="; }; }; in @@ -19,14 +19,10 @@ in { plugin = vim-tidal; config = /* lua */ '' - -- Live-code TidalCycles from .tidal files. The pattern language - -- (GHCi + tidal) and the audio engine (SuperDirt) come from the - -- project's `nix develop` (see the electronic-music flake), not from - -- here -- this plugin only ships lines to a running GHCi. - -- - -- tmux target: -send pastes into a tmux pane running - -- `tidal` (GHCi booted with Tidal). In tmux mode vim-tidal does not - -- start GHCi itself, so launch `tidal` in its own pane first. + -- Ship .tidal lines to a tmux pane running `tidal` (GHCi+Tidal); it + -- does not boot GHCi, so start `tidal` in its own pane first. The + -- pattern language and SuperDirt engine come from the electronic-music + -- flake's devShell, not from here. vim.g.tidal_target = "tmux" ''; extraPackages = with pkgs; [ tmux ]; From 62269060f7241f94536005352f780a636d090e15 Mon Sep 17 00:00:00 2001 From: etrobert-bot Date: Fri, 26 Jun 2026 16:57:19 +0200 Subject: [PATCH 3/5] feat(neovim): format .tidal files with ormolu + haskell treesitter Tidal patterns are Haskell expressions, so reuse the Haskell tooling: - conform runs ormolu over the 'tidal' filetype (ormolu reformats bare pattern lines; stylish-haskell leaves operator spacing untouched, and haskell-language-server needs a cabal/stack project so it can't drive standalone .tidal buffers). - register the 'tidal' filetype against the haskell treesitter grammar for highlighting, and add the grammar to nvim-treesitter. Co-Authored-By: Claude Opus 4.8 --- pkgs/neovim-wrapped/plugins/conform/config.lua | 1 + pkgs/neovim-wrapped/plugins/conform/default.nix | 1 + pkgs/neovim-wrapped/plugins/tidal/default.nix | 4 ++++ pkgs/neovim-wrapped/plugins/treesitter/default.nix | 1 + 4 files changed, 7 insertions(+) diff --git a/pkgs/neovim-wrapped/plugins/conform/config.lua b/pkgs/neovim-wrapped/plugins/conform/config.lua index 28b47c2a..a5f584db 100644 --- a/pkgs/neovim-wrapped/plugins/conform/config.lua +++ b/pkgs/neovim-wrapped/plugins/conform/config.lua @@ -18,6 +18,7 @@ require("conform").setup({ rust = { "rustfmt" }, python = { "isort", "black" }, nix = { "nixfmt" }, + tidal = { "ormolu" }, -- Tidal patterns are Haskell }, format_on_save = { timeout_ms = 1000, diff --git a/pkgs/neovim-wrapped/plugins/conform/default.nix b/pkgs/neovim-wrapped/plugins/conform/default.nix index 34c23e35..a93e08a5 100644 --- a/pkgs/neovim-wrapped/plugins/conform/default.nix +++ b/pkgs/neovim-wrapped/plugins/conform/default.nix @@ -12,6 +12,7 @@ shfmt rustfmt nixfmt + ormolu # haskell formatter, used for .tidal files ]; } ]; diff --git a/pkgs/neovim-wrapped/plugins/tidal/default.nix b/pkgs/neovim-wrapped/plugins/tidal/default.nix index 7196dbbe..d69f415e 100644 --- a/pkgs/neovim-wrapped/plugins/tidal/default.nix +++ b/pkgs/neovim-wrapped/plugins/tidal/default.nix @@ -24,6 +24,10 @@ in -- pattern language and SuperDirt engine come from the electronic-music -- flake's devShell, not from here. vim.g.tidal_target = "tmux" + + -- Tidal patterns are Haskell; drive treesitter highlighting (and + -- ormolu formatting via conform) off the haskell grammar. + vim.treesitter.language.register("haskell", "tidal") ''; extraPackages = with pkgs; [ tmux ]; } diff --git a/pkgs/neovim-wrapped/plugins/treesitter/default.nix b/pkgs/neovim-wrapped/plugins/treesitter/default.nix index 315ac5eb..81095fb1 100644 --- a/pkgs/neovim-wrapped/plugins/treesitter/default.nix +++ b/pkgs/neovim-wrapped/plugins/treesitter/default.nix @@ -29,6 +29,7 @@ typescript json go + haskell # .tidal files (Tidal patterns are Haskell) scheme rust zsh From 66ac33d9f31c8e45049c54c617e251d5a8087b4f Mon Sep 17 00:00:00 2001 From: etrobert-bot Date: Fri, 26 Jun 2026 17:42:22 +0200 Subject: [PATCH 4/5] feat(neovim): format .tidal block-by-block via tidal-fmt Bare ormolu parses the whole file as one Haskell module, which fails on real Tidal: multi-line patterns put a closing ] or # at column 1 (illegal mid-declaration in a module, but fine as a standalone GHCi expression), and one unformattable block makes ormolu bail on the entire file so nothing formats. Add a tidal-fmt wrapper that treats the file as what it is -- a sequence of blank-line-separated GHCi statements -- and formats each block independently: bare ormolu first (correct for simple/adjacent statements), else wrap the block as an indented binding so column-1 continuations parse, else pass the block through untouched (GHCi :t/:set lines, anything unparseable). Idempotent. Wire it into conform as a custom formatter for the tidal filetype, replacing the bare ormolu entry. Co-Authored-By: Claude Opus 4.8 --- .../neovim-wrapped/plugins/conform/config.lua | 7 +- .../plugins/conform/default.nix | 5 +- .../plugins/conform/tidal-fmt.nix | 71 +++++++++++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 pkgs/neovim-wrapped/plugins/conform/tidal-fmt.nix diff --git a/pkgs/neovim-wrapped/plugins/conform/config.lua b/pkgs/neovim-wrapped/plugins/conform/config.lua index a5f584db..2de3d48a 100644 --- a/pkgs/neovim-wrapped/plugins/conform/config.lua +++ b/pkgs/neovim-wrapped/plugins/conform/config.lua @@ -1,4 +1,9 @@ require("conform").setup({ + formatters = { + -- .tidal files are GHCi statement sequences, not Haskell modules, so + -- bare ormolu can't parse them; tidal-fmt formats block-by-block. + tidal_fmt = { command = "tidal-fmt" }, + }, formatters_by_ft = { lua = { "stylua" }, javascript = { "prettierd", "prettier", stop_after_first = true }, @@ -18,7 +23,7 @@ require("conform").setup({ rust = { "rustfmt" }, python = { "isort", "black" }, nix = { "nixfmt" }, - tidal = { "ormolu" }, -- Tidal patterns are Haskell + tidal = { "tidal_fmt" }, }, format_on_save = { timeout_ms = 1000, diff --git a/pkgs/neovim-wrapped/plugins/conform/default.nix b/pkgs/neovim-wrapped/plugins/conform/default.nix index a93e08a5..c4016507 100644 --- a/pkgs/neovim-wrapped/plugins/conform/default.nix +++ b/pkgs/neovim-wrapped/plugins/conform/default.nix @@ -1,4 +1,7 @@ { pkgs, ... }: +let + tidal-fmt = pkgs.callPackage ./tidal-fmt.nix { }; +in { plugins = [ { @@ -12,7 +15,7 @@ shfmt rustfmt nixfmt - ormolu # haskell formatter, used for .tidal files + tidal-fmt # ormolu-based formatter for .tidal files ]; } ]; diff --git a/pkgs/neovim-wrapped/plugins/conform/tidal-fmt.nix b/pkgs/neovim-wrapped/plugins/conform/tidal-fmt.nix new file mode 100644 index 00000000..c0db06b5 --- /dev/null +++ b/pkgs/neovim-wrapped/plugins/conform/tidal-fmt.nix @@ -0,0 +1,71 @@ +{ + writeShellApplication, + ormolu, + gnused, + coreutils, +}: + +# A .tidal file is a sequence of GHCi statements separated by blank lines +# (vim-tidal's block convention), not a Haskell module -- so a whole-file +# Haskell formatter chokes on it. Format each blank-separated block on its own. +writeShellApplication { + name = "tidal-fmt"; + + runtimeInputs = [ + ormolu + gnused + coreutils + ]; + + inheritPath = false; + + text = /* bash */ '' + # Format one block: + # 1. bare ormolu -- correct for simple/adjacent top-level statements; + # 2. else wrap as an indented binding so column-1 continuations (a `]` + # or `#` closing a multi-line pattern) parse, format, then unwrap; + # 3. else emit unchanged (GHCi `:t`/`:set` lines, anything unparseable). + fmt_block() { + local block="$1" out + + if out=$(printf '%s\n' "$block" | ormolu --stdin-input-file b.hs 2>/dev/null) \ + && [[ -n "$out" ]]; then + printf '%s' "$out" + return + fi + + if out=$(printf '_w_ =\n%s\n' "$(printf '%s\n' "$block" | sed 's/^/ /')" \ + | ormolu --stdin-input-file w.hs 2>/dev/null) && [[ -n "$out" ]]; then + printf '%s\n' "$out" | tail --lines=+2 | sed 's/^ //' + return + fi + + printf '%s\n' "$block" + } + + # Read stdin, format each block, rejoin with exactly one blank line between. + block="" + first=1 + + flush() { + [[ -z "$block" ]] && return + local out + out=$(fmt_block "$block") # command substitution strips trailing newlines + if [[ $first -eq 0 ]]; then printf '\n\n'; fi + printf '%s' "$out" + first=0 + block="" + } + + while IFS= read -r line || [[ -n "$line" ]]; do + if [[ -z "$line" ]]; then + flush + else + block+="''${block:+$'\n'}$line" + fi + done + flush + + if [[ $first -eq 0 ]]; then printf '\n'; fi # single trailing newline + ''; +} From eb8582b613ae126bbab585b1f996d73a59580311 Mon Sep 17 00:00:00 2001 From: etrobert-bot Date: Fri, 26 Jun 2026 17:48:21 +0200 Subject: [PATCH 5/5] Revert "feat(neovim): format .tidal block-by-block via tidal-fmt" This reverts commit 66ac33d9f31c8e45049c54c617e251d5a8087b4f. --- .../neovim-wrapped/plugins/conform/config.lua | 7 +- .../plugins/conform/default.nix | 5 +- .../plugins/conform/tidal-fmt.nix | 71 ------------------- 3 files changed, 2 insertions(+), 81 deletions(-) delete mode 100644 pkgs/neovim-wrapped/plugins/conform/tidal-fmt.nix diff --git a/pkgs/neovim-wrapped/plugins/conform/config.lua b/pkgs/neovim-wrapped/plugins/conform/config.lua index 2de3d48a..a5f584db 100644 --- a/pkgs/neovim-wrapped/plugins/conform/config.lua +++ b/pkgs/neovim-wrapped/plugins/conform/config.lua @@ -1,9 +1,4 @@ require("conform").setup({ - formatters = { - -- .tidal files are GHCi statement sequences, not Haskell modules, so - -- bare ormolu can't parse them; tidal-fmt formats block-by-block. - tidal_fmt = { command = "tidal-fmt" }, - }, formatters_by_ft = { lua = { "stylua" }, javascript = { "prettierd", "prettier", stop_after_first = true }, @@ -23,7 +18,7 @@ require("conform").setup({ rust = { "rustfmt" }, python = { "isort", "black" }, nix = { "nixfmt" }, - tidal = { "tidal_fmt" }, + tidal = { "ormolu" }, -- Tidal patterns are Haskell }, format_on_save = { timeout_ms = 1000, diff --git a/pkgs/neovim-wrapped/plugins/conform/default.nix b/pkgs/neovim-wrapped/plugins/conform/default.nix index c4016507..a93e08a5 100644 --- a/pkgs/neovim-wrapped/plugins/conform/default.nix +++ b/pkgs/neovim-wrapped/plugins/conform/default.nix @@ -1,7 +1,4 @@ { pkgs, ... }: -let - tidal-fmt = pkgs.callPackage ./tidal-fmt.nix { }; -in { plugins = [ { @@ -15,7 +12,7 @@ in shfmt rustfmt nixfmt - tidal-fmt # ormolu-based formatter for .tidal files + ormolu # haskell formatter, used for .tidal files ]; } ]; diff --git a/pkgs/neovim-wrapped/plugins/conform/tidal-fmt.nix b/pkgs/neovim-wrapped/plugins/conform/tidal-fmt.nix deleted file mode 100644 index c0db06b5..00000000 --- a/pkgs/neovim-wrapped/plugins/conform/tidal-fmt.nix +++ /dev/null @@ -1,71 +0,0 @@ -{ - writeShellApplication, - ormolu, - gnused, - coreutils, -}: - -# A .tidal file is a sequence of GHCi statements separated by blank lines -# (vim-tidal's block convention), not a Haskell module -- so a whole-file -# Haskell formatter chokes on it. Format each blank-separated block on its own. -writeShellApplication { - name = "tidal-fmt"; - - runtimeInputs = [ - ormolu - gnused - coreutils - ]; - - inheritPath = false; - - text = /* bash */ '' - # Format one block: - # 1. bare ormolu -- correct for simple/adjacent top-level statements; - # 2. else wrap as an indented binding so column-1 continuations (a `]` - # or `#` closing a multi-line pattern) parse, format, then unwrap; - # 3. else emit unchanged (GHCi `:t`/`:set` lines, anything unparseable). - fmt_block() { - local block="$1" out - - if out=$(printf '%s\n' "$block" | ormolu --stdin-input-file b.hs 2>/dev/null) \ - && [[ -n "$out" ]]; then - printf '%s' "$out" - return - fi - - if out=$(printf '_w_ =\n%s\n' "$(printf '%s\n' "$block" | sed 's/^/ /')" \ - | ormolu --stdin-input-file w.hs 2>/dev/null) && [[ -n "$out" ]]; then - printf '%s\n' "$out" | tail --lines=+2 | sed 's/^ //' - return - fi - - printf '%s\n' "$block" - } - - # Read stdin, format each block, rejoin with exactly one blank line between. - block="" - first=1 - - flush() { - [[ -z "$block" ]] && return - local out - out=$(fmt_block "$block") # command substitution strips trailing newlines - if [[ $first -eq 0 ]]; then printf '\n\n'; fi - printf '%s' "$out" - first=0 - block="" - } - - while IFS= read -r line || [[ -n "$line" ]]; do - if [[ -z "$line" ]]; then - flush - else - block+="''${block:+$'\n'}$line" - fi - done - flush - - if [[ $first -eq 0 ]]; then printf '\n'; fi # single trailing newline - ''; -}