tpope/vim-rails

Treesitter seems to interfere with after ftplugin and definition of gf keymaps

phallguy opened this issue · 18 comments

First off thanks for even making this package. I found an issue recently when trying to navigate a rails codebase and trying to get more efficient with the custom commands and keymaps that vim-rails offers. Eview/Econ and friends work great. But if I'm on a render "path/to/partial" line in an erb file it fails with E345: Can't fine file "path/to/partial" in path" error.

After a bunch of trial and error I've narrowed the problem down to the use of Treesitter but can't explain what the interaction is that is causing the problem. I've included a minimum config that reproduces the problem in a rails app.

Here's whats fun -- if I :TSDisable highlight the gf keymap works as expected. If I :TSEnable highlight again it breaks again. I'v exhausted my ability to debug further but was hoping I might provide enough info that you'd be able to see the problem pretty easily.

I ended up working around the issue by adding my own ftdetect plugin

" nvim/ftdetect/rails.vim
au BufRead,BufNewFile *		if RailsDetect() | call rails#ruby_setup() | endif
-- init.lua
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
	vim.fn.system({
		"git",
		"clone",
		"--filter=blob:none",
		"https://github.com/folke/lazy.nvim.git",
		"--branch=stable", -- latest stable release
		lazypath,
	})
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
	{
		"tpope/vim-rails",
	},
	{
		"nvim-treesitter/nvim-treesitter",
		config = function()
			require("nvim-treesitter.configs").setup({
				-- Add languages to be installed here that you want installed for treesitter
				ensure_installed = { "ruby", "embedded_template" },
				-- Autoinstall languages that are not installed. Defaults to false (but you can change for yourself!)
				auto_install = true,
				highlight = {
					enable = true,
				},
			})
		end,
	},
})

-- vim: ts=2 sts=2 sw=2 et

with TSEnable highlight

verb nmap gf

n  gf          &@<SNR>28_c:find <SNR>28_<cfile><CR>
        Last set from /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/ftplugin/ruby.vim line 161

:scriptnames

  1: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/ftplugin.vim
  2: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/indent.vim
  3: ~/.config/rails/init.lua
  4: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/filetype.lua
  5: ~/.local/share/rails/lazy/nvim-treesitter/plugin/nvim-treesitter.lua
  6: ~/.local/share/rails/lazy/vim-rails/plugin/rails.vim
  7: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/editorconfig.lua
  8: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/gzip.vim
  9: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/health.vim
 10: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/man.lua
 11: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/matchit.vim
 12: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/pack/dist/opt/matchit/plugin/matchit.vim
 13: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/matchparen.vim
 14: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/netrwPlugin.vim
 15: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/nvim.lua
 16: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/rplugin.vim
 17: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/shada.vim
 18: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/spellfile.vim
 19: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/tarPlugin.vim
 20: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/tohtml.vim
 21: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/tutor.vim
 22: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/zipPlugin.vim
 23: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/syntax.vim
 24: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/synload.vim
 25: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/ftplugin/eruby.vim
 26: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/ftplugin/html.vim
 27: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/autoload/htmlcomplete.vim
 28: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/ftplugin/ruby.vim
 29: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/autoload/provider/ruby.vim
 30: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/autoload/remote/host.vim
 31: ~/.local/share/rails/lazy/vim-rails/after/ftplugin/ruby/rails.vim
 32: ~/.local/share/rails/lazy/vim-rails/autoload/rails.vim
 33: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/indent/eruby.vim
 34: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/indent/ruby.vim
 35: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/indent/html.vim
 36: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/indent/javascript.vim
 37: ~/.local/share/rails/lazy/vim-rails/compiler/rails.vim
 38: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/compiler/rake.vim
 39: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/autoload/syntaxcomplete.vim
 40: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/eruby.vim
 41: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/html.vim
 42: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/xml.vim
 43: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/dtd.vim
 44: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/javascript.vim
 45: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/vb.vim
 46: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/css.vim
 47: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/ruby.vim
 48: ~/.local/share/rails/lazy/vim-rails/after/syntax/eruby/rails.vim
 49: ~/.local/share/rails/lazy/vim-rails/after/syntax/ruby/rails.vim

with TSDisable highlight

verb nmap gf

n  gf          &@<SNR>28_c:find <SNR>28_<cfile><CR>
        Last set from /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/ftplugin/ruby.vim line 161

:scriptnames

:scriptnames
  1: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/ftplugin.vim
  2: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/indent.vim
  3: ~/.config/rails/init.lua
  4: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/filetype.lua
  5: ~/.local/share/rails/lazy/nvim-treesitter/plugin/nvim-treesitter.lua
  6: ~/.local/share/rails/lazy/vim-rails/plugin/rails.vim
  7: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/editorconfig.lua
  8: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/gzip.vim
  9: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/health.vim
 10: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/man.lua
 11: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/matchit.vim
 12: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/pack/dist/opt/matchit/plugin/matchit.vim
 13: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/matchparen.vim
 14: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/netrwPlugin.vim
 15: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/nvim.lua
 16: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/rplugin.vim
 17: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/shada.vim
 18: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/spellfile.vim
 19: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/tarPlugin.vim
 20: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/tohtml.vim
 21: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/tutor.vim
 22: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/plugin/zipPlugin.vim
 23: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/syntax.vim
 24: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/synload.vim
 25: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/ftplugin/eruby.vim
 26: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/ftplugin/html.vim
 27: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/autoload/htmlcomplete.vim
 28: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/ftplugin/ruby.vim
 29: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/autoload/provider/ruby.vim
 30: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/autoload/remote/host.vim
 31: ~/.local/share/rails/lazy/vim-rails/after/ftplugin/ruby/rails.vim
 32: ~/.local/share/rails/lazy/vim-rails/autoload/rails.vim
 33: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/indent/eruby.vim
 34: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/indent/ruby.vim
 35: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/indent/html.vim
 36: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/indent/javascript.vim
 37: ~/.local/share/rails/lazy/vim-rails/compiler/rails.vim
 38: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/compiler/rake.vim
 39: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/autoload/syntaxcomplete.vim
 40: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/eruby.vim
 41: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/html.vim
 42: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/xml.vim
 43: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/dtd.vim
 44: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/javascript.vim
 45: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/vb.vim
 46: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/css.vim
 47: /opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim/runtime/syntax/ruby.vim
 48: ~/.local/share/rails/lazy/vim-rails/after/syntax/eruby/rails.vim
 49: ~/.local/share/rails/lazy/vim-rails/after/syntax/ruby/rails.vim

If it happens to be useful I am using a nightly build of neovim

❯ nvim --version
NVIM v0.10.0-dev-1390+g0451391ec-Homebrew
Build type: Release
LuaJIT 2.1.0-beta3

   system vimrc file: "$VIM/sysinit.vim"
  fall-back for $VIM: "/opt/homebrew/Cellar/neovim/HEAD-0451391/share/nvim"

Run :checkhealth for more info
:TSConfigInfo
{
  auto_install = true,
  ensure_installed = { "ruby", "embedded_template" },
  ignore_install = {},
  modules = {
    auto_install = true,
    highlight = {
      additional_vim_regex_highlighting = false,
      custom_captures = {},
      disable = {},
      enable = true,
      loaded = true,
      module_path = "nvim-treesitter.highlight"
    },
    incremental_selection = {
      disable = {},
      enable = false,
      keymaps = {
        init_selection = "gnn",
        node_decremental = "grm",
        node_incremental = "grn",
        scope_incremental = "grc"
      },
      module_path = "nvim-treesitter.incremental_selection"
    },
    indent = {
      disable = {},
      enable = false,
      module_path = "nvim-treesitter.indent"
    }
  },
  sync_install = false
}
tpope commented

The gf implementation explicitly relies on Vim's syntax highlighting. I'm not sure what it would take to get it to work with Treesitter.

vim-rails/autoload/rails.vim

Lines 2616 to 2671 in eb51379

let decl = matchlist(getline('.'),
\ '^\(\s*\)\(\w\+\)\>\%\(\s\+\|\s*(\s*\):\=\([''"]\=\)\(\%(\w\|::\)\+\)\3')
if len(decl) && len(decl[0]) >= col('.')
let declid = synID(line('.'), 1+len(decl[1]), 1)
let declbase = rails#underscore(decl[4], 1)
if declid ==# hlID('rubyEntities')
return rails#singularize(declbase) . '.rb'
elseif declid ==# hlID('rubyEntity') || decl[4] =~# '\u'
return declbase . '.rb'
elseif index([hlID('rubyMacro'), hlID('rubyAttribute')], declid) >= 0
return rails#singularize(declbase) . '.rb'
endif
endif
let synid = synID(line('.'), col('.'), 1)
let synstring = synid == hlID('rubyString') || synid == hlID('rubyBackslashEscape')
let old_isfname = &isfname
try
if synstring
set isfname+=:
let cfile = expand("<cfile>")
else
set isfname=@,48-57,/,-,_,:,#
let cfile = expand("<cfile>")
if cfile !~# '\u\|/'
let cfile = s:sub(cfile, '_attributes$', '')
let cfile = rails#singularize(cfile)
let cfile = s:sub(cfile, '_ids=$', '')
endif
endif
finally
let &isfname = old_isfname
endtry
let cfile = s:sub(cfile, '^:=[:@]', '')
let cfile = s:sub(cfile, ':0x\x+$', '') " For #<Object:0x...> style output
if cfile =~# '^\l\w*#\w\+$'
let cfile = s:sub(cfile, '#', '_controller.rb#')
elseif cfile =~# '^\u[[:alnum:]]*\%($\|::\)'
let cfile = s:file_for_nested_constant(cfile)
elseif cfile =~# '^\w*_\%(path\|url\)$' && !synstring
let route = s:gsub(cfile, '^hash_for_|_%(path|url)$', '')
let cfile = s:active() ? rails#app().named_route_file(route) : ''
if empty(cfile)
let cfile = s:sub(route, '^formatted_', '')
if cfile =~# '^\%(new\|edit\)_'
let cfile = s:sub(rails#pluralize(cfile), '^(new|edit)_(.*)', '\2_controller.rb#\1')
elseif cfile ==# rails#singularize(cfile)
let cfile = rails#pluralize(cfile).'_controller.rb#show'
else
let cfile = cfile.'_controller.rb#index'
endif
endif
elseif cfile !~# '\.' && !synstring
let cfile .= '.rb'
endif
return cfile

OK, did some more debugging...found that which-key was also complicating things. With my ftdetect hack (not even sure if thats the right approach) I also had to disable which-key intercept of the gf trigger.

With this config, and my ftdetect hack, Treesitter doesn't seem to interfere anymore

	{
		"folke/which-key.nvim",
		opts = {
			triggers_blacklist = {
				n = { "g", "gf" }
			}
		},
	},

Not sure if there's anything to do with the vim-rails plugin to make this better for others but I've found something that works for me. Thanks again for the help and the amazing plugins!

tpope commented

gf still requires Vim syntax highlighting for maximum functionality.

jiz4oh commented

thanks @phallguy has inspired me to resolve it by

autocmd FileType eruby
      \ if RailsDetect() | call rails#ruby_setup() | endif
tpope commented

Resolve what? It's already working.

jiz4oh commented

Hi @tpope, the commit do not resolve the question

image

image

but if I recall rails#ruby_setup() on this buffer, the gf is worked now

tpope commented

It's normally called here. I can't think of a reason why enabling Treesitter would cause this not to load.

jiz4oh commented

The key code is

image

if I comment this line, my workaround is not working too. and it's still working if I comment other lines inside the function except this line.

image

jiz4oh commented

I have checked the map by echo maparg('<Plug><cfile>', 'c')

image

and it's seems like ErubyAtCursor function return difference value between :TSEnable highlight and :TSDisable highlight

after :TSEnable highlight, echo ErubyAtCursor() return 0.
after :TSDisable highlight, echo ErubyAtCursor() return 1.

jiz4oh commented

if I recall rails#ruby_setup() on this buffer, echo maparg('<Plug><cfile>', 'c') return rails#ruby_cfile(). it is no longer judged by ErubyAtCursor and it's worked

tpope commented

Oh yeah that explains it. ErubyAtCursor() requires Vim syntax highlighting to work. A cleaner workaround would be to put this in after/ftplugin/eruby.vim:

function! ErubyAtCursor() abort
  return 1
endfunction

The fix would be to change this function to support Treesitter.

Oddly enough, if I do this once after starting vim, almost everything works better/as expected

:TSDisable highlight
:TSEnable highlight

Totally grasping at straws and have no idea what I'm doing...but it works? 😄

jiz4oh commented

@phallguy even if without your own ftdetect plugin?

jiz4oh commented

thanks @tpope, Iis it possible for the workaround to become the default setting for the plugin in the future?

tpope commented

The point of ErubyAtCursor() is to make gf behave differently in the Ruby versus the non-Ruby (typically HTML) parts. The workaround breaks it so it treats the whole file as Ruby. So no, I will not be breaking the default for everybody just because it's better than nothing for Treesitter users. The minimum bar for a workaround has to be that non-Treesitter users are unaffected.

tpope commented

Oddly enough, if I do this once after starting vim, almost everything works better/as expected

:TSDisable highlight
:TSEnable highlight

Totally grasping at straws and have no idea what I'm doing...but it works? smile

Not sure what's going on here, but if I had to guess, it leaves it in a state where both Treesitter and Vim highlighting are on at the same time? This might have other side effects but from the perspective of rails.vim it's perfect, the best of both worlds.

@tpope Yep! best of both. Feels good. Really to appreciate the help and attention.

@jiz4oh Here's what I ended up

" nvim/after/plugin/rails_detection.vim
autocmd FileType ruby
      \ if RailsDetect() | call rails#ruby_setup() | endif
autocmd FileType eruby
      \ if RailsDetect() | call rails#ruby_setup() | endif
-- nvim/lua/user/ruby.lua (required from init.lua)
local group = vim.api.nvim_create_augroup("RubyEx", { clear = true })
local initialized_ruby_syntax = false
vim.api.nvim_create_autocmd("FileType", {
	group = group,
	pattern = { "ruby", "eruby" },
	callback = function()
		if not initialized_ruby_syntax then
			vim.cmd([[TSDisable highlight]])
			vim.cmd([[TSEnable highlight]])

			initialized_ruby_syntax = true
		end
	end,
})

jiz4oh commented

@tpope Make sense, so much appreciate the help and attention.

@phallguy appreciate for your sharing