jparise / vim-graphql

A Vim plugin that provides GraphQL file detection, syntax highlighting, and indentation.
MIT License
487 stars 25 forks source link

Automatic indentation inside JavaScript template literals not working as expected #87

Closed eisukeesaki closed 1 year ago

eisukeesaki commented 2 years ago

issue

I was expecting the plugin to automatically insert indentations inside JavaScript template literals, but it does not. Is it likely that something is interfering with the functionality or does it not work well with Neovim?

I tried enabling and disabling autoindent and smartindent but it did not change the behavior.

https://user-images.githubusercontent.com/33922926/178836722-43e6e412-9b25-4353-b569-1cb8fb08727a.mov

environment

NVIM v0.7.2 Build type: Release LuaJIT 2.1.0-beta3 Compiled by brew@BigSur

macOS 11.6.7 (20G630) Intel

jparise commented 2 years ago

It should work as you expect, so we'll need to debug the issue.

Are you using any plugins that extend vim's basic JavaScript behavior?

eisukeesaki commented 2 years ago

Here is a list of plugins that I'm using.


  Plug 'tpope/vim-fugitive'
  Plug 'tpope/vim-rhubarb'
  Plug 'hoob3rt/lualine.nvim'
  Plug 'kristijanhusak/defx-git'
  Plug 'kristijanhusak/defx-icons'
  Plug 'Shougo/defx.nvim', { 'do': ':UpdateRemotePlugins' }
  Plug 'neovim/nvim-lspconfig'
  Plug 'williamboman/nvim-lsp-installer'
  Plug 'tami5/lspsaga.nvim'
  Plug 'folke/lsp-colors.nvim'
  Plug 'L3MON4D3/LuaSnip'
  Plug 'hrsh7th/cmp-nvim-lsp'
  Plug 'hrsh7th/cmp-buffer'
  Plug 'hrsh7th/nvim-cmp'
  Plug 'nvim-treesitter/nvim-treesitter', { 'do': ':TSUpdate' }
  Plug 'kyazdani42/nvim-web-devicons'
  Plug 'onsails/lspkind-nvim'
  Plug 'nvim-lua/popup.nvim'
  Plug 'nvim-lua/plenary.nvim'
  Plug 'nvim-telescope/telescope.nvim'
  Plug 'windwp/nvim-autopairs'
  Plug 'windwp/nvim-ts-autotag'
  Plug 'tpope/vim-commentary'
  Plug 'jeetsukumaran/vim-indentwise'
  Plug 'vimwiki/vimwiki'
  Plug 'tpope/vim-sleuth'
  Plug 'jparise/vim-graphql'
  Plug 'groenewege/vim-less', { 'for': 'less' }
  Plug 'kchmck/vim-coffee-script', { 'for': 'coffee' }
eisukeesaki commented 2 years ago

This is my init.vim

" Fundamentals "{{{
" ---------------------------------------------------------------------

" init autocmd
autocmd!
" set script encoding
scriptencoding utf-8
" stop loading config if it's on tiny or small
if !1 | finish | endif

set nocompatible
set number
syntax enable
set fileencodings=utf-8,sjis,euc-jp,latin
set encoding=utf-8
set title
" set autoindent
set background=dark
set nobackup
set hlsearch
set showcmd
set cmdheight=1
set laststatus=2
set scrolloff=10
set expandtab
"let loaded_matchparen = 1
set shell=fish
set backupskip=/tmp/*,/private/tmp/*

" incremental substitution (neovim)
if has('nvim')
  set inccommand=split
endif

" Suppress appending <PasteStart> and <PasteEnd> when pasting
set t_BE=

set nosc noru nosm
" Don't redraw while executing macros (good performance config)
set lazyredraw
"set showmatch
" How many tenths of a second to blink when matching brackets
"set mat=2
" Ignore case when searching
set ignorecase
" Be smart when using tabs ;)
set smarttab
" indents
filetype plugin indent on
set shiftwidth=2
set tabstop=2
set ai "Auto indent
set si "Smart indent
set nowrap "No Wrap lines
set backspace=start,eol,indent
" Finding files - Search down into subfolders
set path+=**
set wildignore+=*/node_modules/*

" Turn off paste mode when leaving insert
autocmd InsertLeave * set nopaste

" Add asterisks in block comments
" set formatoptions+=r
set shortmess=I

"}}}

" Highlights "{{{
" ---------------------------------------------------------------------
set cursorline
"set cursorcolumn

" Set cursor line color on visual mode
highlight Visual cterm=NONE ctermbg=236 ctermfg=NONE guibg=Grey40

highlight LineNr cterm=none ctermfg=240 guifg=#2b506e guibg=#000000

augroup BgHighlight
  autocmd!
  autocmd WinEnter * set cul
  autocmd WinLeave * set nocul
augroup END

if &term =~ "screen"
  autocmd BufEnter * if bufname("") !~ "^?[A-Za-z0-9?]*://" | silent! exe '!echo -n "\ek[`hostname`:`basename $PWD`/`basename %`]\e\\"' | endif
  autocmd VimLeave * silent!  exe '!echo -n "\ek[`hostname`:`basename $PWD`]\e\\"'
endif

"}}}

" File types "{{{
" ---------------------------------------------------------------------
" JavaScript
au BufNewFile,BufRead *.es6 setf javascript
" TypeScript
au BufNewFile,BufRead *.tsx setf typescriptreact
" Markdown
au BufNewFile,BufRead *.md set filetype=markdown
au BufNewFile,BufRead *.mdx set filetype=markdown
" Flow
au BufNewFile,BufRead *.flow set filetype=javascript
" Fish
au BufNewFile,BufRead *.fish set filetype=fish

set suffixesadd=.js,.es,.jsx,.json,.css,.less,.sass,.styl,.php,.py,.md

autocmd FileType coffee setlocal shiftwidth=2 tabstop=2
autocmd FileType ruby setlocal shiftwidth=2 tabstop=2
autocmd FileType yaml setlocal shiftwidth=2 tabstop=2

"}}}

" Imports "{{{
" ---------------------------------------------------------------------
runtime ./plug.vim
if has("unix")
  let s:uname = system("uname -s")
  " Do Mac stuff
  if s:uname == "Darwin\n"
    runtime ./macos.vim
  endif
endif
if has('win32')
  runtime ./windows.vim
endif

runtime ./maps.vim
runtime ./commands.vim
"}}}

" Syntax theme "{{{
" ---------------------------------------------------------------------

" true color
if exists("&termguicolors") && exists("&winblend")
  syntax enable
  set termguicolors
  set winblend=0
  set wildoptions=pum
  set pumblend=5
  set background=dark
  " Use NeoSolarized
  let g:neosolarized_termtrans=1
  runtime ./colors/NeoSolarized.vim
  colorscheme NeoSolarized
endif

"}}}

" LSP Diagnostic "{{{
" ---------------------------------------------------------------------
autocmd BufEnter * lua vim.diagnostic.disable()

" Extras "{{{
" ---------------------------------------------------------------------
set exrc
"}}}

" vim: set foldmethod=marker foldlevel=0:
eisukeesaki commented 2 years ago

Here is my lspconfig.rc.vim

if !exists('g:lspconfig')
  finish
endif

lua << EOF
--vim.lsp.set_log_level("debug")
EOF

lua << EOF
local nvim_lsp = require('lspconfig')
local protocol = require'vim.lsp.protocol'

-- Use an on_attach function to only map the following keys 
-- after the language server attaches to the current buffer
local on_attach = function(client, bufnr)
  local function buf_set_keymap(...) vim.api.nvim_buf_set_keymap(bufnr, ...) end
  local function buf_set_option(...) vim.api.nvim_buf_set_option(bufnr, ...) end

  --Enable completion triggered by <c-x><c-o>
  buf_set_option('omnifunc', 'v:lua.vim.lsp.omnifunc')

  -- Mappings.
  local opts = { noremap=true, silent=true }

  -- See `:help vim.lsp.*` for documentation on any of the below functions
  buf_set_keymap('n', 'gD', '<Cmd>lua vim.lsp.buf.declaration()<CR>', opts)
  buf_set_keymap('n', 'gd', '<Cmd>lua vim.lsp.buf.definition()<CR>', opts)
  buf_set_keymap('n', 'K', '<Cmd>lua vim.lsp.buf.hover()<CR>', opts)
  buf_set_keymap('n', 'gi', '<cmd>lua vim.lsp.buf.implementation()<CR>', opts)
  buf_set_keymap('i', '<C-k>', '<cmd>lua vim.lsp.buf.signature_help()<CR>', opts)
  buf_set_keymap('n', '<space>wa', '<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>', opts)
  buf_set_keymap('n', '<space>wr', '<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>', opts)
  buf_set_keymap('n', '<space>wl', '<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>', opts)
  buf_set_keymap('n', '<space>D', '<cmd>lua vim.lsp.buf.type_definition()<CR>', opts)
  buf_set_keymap('n', '<space>rn', '<cmd>lua vim.lsp.buf.rename()<CR>', opts)
  buf_set_keymap('n', '<space>ca', '<cmd>lua vim.lsp.buf.code_action()<CR>', opts)
  buf_set_keymap('n', 'gr', '<cmd>lua vim.lsp.buf.references()<CR>', opts)
  buf_set_keymap('n', '<space>e', '<cmd>lua vim.lsp.diagnostic.show_line_diagnostics()<CR>', opts)
  --buf_set_keymap('n', '<space>e', '<cmd>lua vim.diagnostic.open_float()<CR>', opts)
  --buf_set_keymap('n', '<space>e', '<cmd>lua vim.lsp.util.open_floating_preview({scope="line"})<CR>', opts)
  buf_set_keymap('n', '<S-C-k>', '<cmd>lua vim.lsp.diagnostic.goto_prev()<CR>', opts)
  buf_set_keymap('n', '<S-C-j>', '<cmd>lua vim.lsp.diagnostic.goto_next()<CR>', opts)
  --buf_set_keymap('n', '<space>q', '<cmd>lua vim.lsp.diagnostic.set_loclist()<CR>', opts) --deprecated
  buf_set_keymap('n', '<space>q', '<cmd>lua vim.diagnostic.setloclist()<CR>', opts)
  buf_set_keymap("n", "<space>s", "<cmd>lua vim.lsp.buf.formatting()<CR>", opts)

  -- formatting
  if client.name == 'tsserver' then
    client.resolved_capabilities.document_formatting = false
  end

  if client.resolved_capabilities.document_formatting then
    vim.api.nvim_command [[augroup Format]]
    vim.api.nvim_command [[autocmd! * <buffer>]]
    vim.api.nvim_command [[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]]
    vim.api.nvim_command [[augroup END]]
  end

  --protocol.SymbolKind = { }
  protocol.CompletionItemKind = {
    '', -- Text
    '', -- Method
    '', -- Function
    '', -- Constructor
    '', -- Field
    '', -- Variable
    '', -- Class
    'ﰮ', -- Interface
    '', -- Module
    '', -- Property
    '', -- Unit
    '', -- Value
    '', -- Enum
    '', -- Keyword
    '﬌', -- Snippet
    '', -- Color
    '', -- File
    '', -- Reference
    '', -- Folder
    '', -- EnumMember
    '', -- Constant
    '', -- Struct
    '', -- Event
    'ﬦ', -- Operator
    '', -- TypeParameter
  }
end

-- Set up completion using nvim_cmp with LSP source
local capabilities = require('cmp_nvim_lsp').update_capabilities(
  vim.lsp.protocol.make_client_capabilities()
)

nvim_lsp.flow.setup {
  on_attach = on_attach,
  capabilities = capabilities
}

nvim_lsp.tsserver.setup {
  on_attach = on_attach,
  filetypes = { "typescript", "typescriptreact", "typescript.tsx" },
  capabilities = capabilities
}

nvim_lsp.diagnosticls.setup {
  on_attach = on_attach,
  filetypes = { 'javascript', 'javascriptreact', 'json', 'typescript', 'typescriptreact', 'css', 'less', 'scss', 'pandoc' },
  init_options = {
    linters = {
      eslint = {
        command = 'eslint_d',
        rootPatterns = { '.git' },
        debounce = 100,
        args = { '--stdin', '--stdin-filename', '%filepath', '--format', 'json' },
        sourceName = 'eslint_d',
        parseJson = {
          errorsRoot = '[0].messages',
          line = 'line',
          column = 'column',
          endLine = 'endLine',
          endColumn = 'endColumn',
          message = '[eslint] ${message} [${ruleId}]',
          security = 'severity'
        },
        securities = {
          [2] = 'error',
          [1] = 'warning'
        }
      },
    },
    filetypes = {
      javascript = 'eslint',
      javascriptreact = 'eslint',
      typescript = 'eslint',
      typescriptreact = 'eslint',
    },
    formatters = {
      eslint_d = {
        command = 'eslint_d',
        rootPatterns = { '.git' },
        args = { '--stdin', '--stdin-filename', '%filename', '--fix-to-stdout' },
        rootPatterns = { '.git' },
      },
      prettier = {
        command = 'prettier_d_slim',
        rootPatterns = { '.git' },
        -- requiredFiles: { 'prettier.config.js' },
        args = { '--stdin', '--stdin-filepath', '%filename' }
      }
    },
    formatFiletypes = {
      css = 'prettier',
      javascript = 'prettier',
      javascriptreact = 'prettier',
      json = 'prettier',
      scss = 'prettier',
      less = 'prettier',
      typescript = 'prettier',
      typescriptreact = 'prettier',
      json = 'prettier',
    }
  }
}

-- icon
vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
  vim.lsp.diagnostic.on_publish_diagnostics, {
    underline = true,
    -- This sets the spacing and the prefix, obviously.
    virtual_text = {
      spacing = 4,
      prefix = ''
    }
  }
)

EOF
eisukeesaki commented 2 years ago

I've just recently started to learn how vim/nvim works, and most of the configs weren't written by hand by me, so I can't really tell which of the plugins or configs may be interfering with the plugin. I hope the 3 files that I've just posted tells something. If you need more information, I will provide them.

jparise commented 2 years ago

I just verified that this is working (for me and in the unit tests) in both vim 9.0 and neovim 0.7.2, so it feels like something in your configuration is causing the issue.

Inside of your JavaScript buffer, could you tell me the values of these two options: indentexpr and indentkeys?

For me (both vim and neovim are similar):

:verbose set indentexpr
  indentexpr=GetJavascriptGraphQLIndent()
        Last set from ~/.local/share/nvim/plugged/vim-graphql/after/indent/javascript.vim line 33
:verbose set indentkeys
  indentkeys=0{,0},0),0],:,0#,!^F,o,O,e,0],0)
        Last set from /opt/homebrew/Cellar/neovim/0.7.2_1/share/nvim/runtime/indent/javascript.vim line 16
eisukeesaki commented 2 years ago

Thank you for running the tests. In my Neovim environment, I get these values.

:verbose set indentexpr
  indentexpr=nvim_treesitter#indent()
        Last set from Lua

:verbose set indentkeys
  indentkeys=0{,0},0),0],:,0#,!^F,o,O,e,0],0)
        Last set from /usr/local/Cellar/neovim/0.7.2/share/nvim/runtime/indent/javascript.vim line 16
eisukeesaki commented 2 years ago

This is my ~/.config/nvim/after/plugin/treesitter.rc.vim

if !exists('g:loaded_nvim_treesitter')
  echom "Not loaded treesitter"
  finish
endif

lua <<EOF
require'nvim-treesitter.configs'.setup {
  highlight = {
    enable = true,
    disable = {},
  },
  indent = {
    enable = true,
    disable = {},
  },
  ensure_installed = {
    "tsx",
    "toml",
    "fish",
    "php",
    "json",
    "yaml",
    "swift",
    "html",
    "scss"
  },
  autotag = {
    enable = true,
  }
}

local parser_config = require "nvim-treesitter.parsers".get_parser_configs()
parser_config.tsx.filetype_to_parsername = { "javascript", "typescript.tsx" }
EOF
jparise commented 2 years ago

This is the root issue:

indentexpr=nvim_treesitter#indent()

It's preventing the vim-graphql plugin from injecting its own indentation function, which happens here:

https://github.com/jparise/vim-graphql/blob/4bf5d33bda83117537aa3c117dee5b9b14fc9333/after/indent/javascript.vim#L32-L33

I don't have a solution for that at the moment. (I don't currently use neovim or the tree-sitter plugin myself.)

If looks like https://github.com/bkegley/tree-sitter-graphql provides an GraphQL implementation for tree-sitter which might be a better fit for your setup.

eisukeesaki commented 2 years ago

I disabled the nvim-treesitter's indentation function by configuring my treesitter.rc.vim as follows.

...

lua <<EOF
require'nvim-treesitter.configs'.setup {
  ...
  indent = {
    enable = true,
    disable = { "graphql", "javascript" },
  },
  ...
}

local parser_config = require "nvim-treesitter.parsers".get_parser_configs()
parser_config.tsx.filetype_to_parsername = { "javascript", "typescript.tsx" }
EOF

Then made sure that vim-graphql's indentation function is loaded inside a JavaScript buffer.

:verbose set indentexpr
  indentexpr=GetJavascriptGraphQLIndent()
        Last set from ~/.local/share/nvim/plugged/vim-graphql/after/indent/javascript.vim line 33

And now, the plugin behaves like the following.

https://user-images.githubusercontent.com/33922926/179345662-bbcddf0d-e35f-4811-a639-5763cdc5bda1.mov

I was not expecting the following 2 behaviors.

  1. indentation is not inserted at the first line within `` (I.e. line2.).
  2. extra indentation is inserted at the second field within {} block (I.e. line4).

Is this behavior intentional?

eisukeesaki commented 2 years ago

The behavior is the same for un-tagged template literals.

jparise commented 2 years ago
  • indentation is not inserted at the first line within `` (I.e. line2.).

This is the current expected behavior. It's due to the fact that we're embedding the GraphQL syntax (which wouldn't have any leading indentation in a standalone .graphql file) inside of a JavaScript context, where leading indentation would make sense.

I agree this could be improved, though.

  • extra indentation is inserted at the second field within {} block (I.e. line4).

This isn't expected, and I don't have an explanation for it yet. Does this also happen in a stand-alone .graphql file, or only when embedding inside of a JavaScript context?

eisukeesaki commented 2 years ago

Does this also happen in a stand-alone .graphql file

The extra indentation is not inserted in a .graphql file.