Vim remains one of the most powerful and efficient text editors, favored by developers who value modal editing, extensive customization, and terminal-based workflows. The Geode GQL plugin for Vim brings comprehensive graph database development support including syntax highlighting, intelligent omni-completion, query execution, linting integration, and seamless workflow tools.

While Neovim users may prefer the native LSP integration, classic Vim users can achieve excellent GQL development experiences through the Geode Vim plugin, which provides syntax files, filetype detection, omnicompletion via external tools, ALE linter integration, and convenient commands for query execution.

This guide covers installation, configuration, features, key mappings, integration with popular Vim plugins, and best practices for productive GQL development in Vim.

Installation

Prerequisites

Ensure you have the following installed:

# Vim 8.0+ required (or Vim 7.4 with patches)
vim --version | head -1

# Geode CLI must be in PATH
which geode
geode --version

# Optional: ctags for tag generation
which ctags

Using vim-plug

The most popular Vim plugin manager:

" ~/.vimrc
call plug#begin('~/.vim/plugged')

" Geode GQL plugin
Plug 'geodedb/vim-geode-gql'

" Recommended companion plugins
Plug 'dense-analysis/ale'           " Async linting
Plug 'prabirshrestha/vim-lsp'       " LSP support (optional)
Plug 'prabirshrestha/asyncomplete.vim'  " Async completion

call plug#end()

Install plugins:

:PlugInstall

Using Vundle

" ~/.vimrc
set nocompatible
filetype off
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()

Plugin 'VundleVim/Vundle.vim'
Plugin 'geodedb/vim-geode-gql'
Plugin 'dense-analysis/ale'

call vundle#end()
filetype plugin indent on

Install:

:PluginInstall

Using Pathogen

# Clone to bundle directory
cd ~/.vim/bundle
git clone https://github.com/geodedb/vim-geode-gql.git

Using Native Vim Packages (Vim 8+)

# Create pack directory
mkdir -p ~/.vim/pack/geode/start

# Clone plugin
git clone https://github.com/geodedb/vim-geode-gql.git \
  ~/.vim/pack/geode/start/vim-geode-gql

Manual Installation

# Download and extract
curl -LO https://github.com/geodedb/vim-geode-gql/archive/main.tar.gz
tar xzf main.tar.gz

# Copy files
cp -r vim-geode-gql-main/syntax/* ~/.vim/syntax/
cp -r vim-geode-gql-main/ftplugin/* ~/.vim/ftplugin/
cp -r vim-geode-gql-main/ftdetect/* ~/.vim/ftdetect/
cp -r vim-geode-gql-main/autoload/* ~/.vim/autoload/
cp -r vim-geode-gql-main/plugin/* ~/.vim/plugin/

Configuration

Basic Setup

Add to your .vimrc:

" ~/.vimrc

" Enable file type detection and plugins
filetype plugin indent on
syntax on

" Geode GQL configuration
let g:geode_server_host = 'localhost'
let g:geode_server_port = 3141
let g:geode_default_database = 'default'
let g:geode_auto_connect = 1

" Query execution settings
let g:geode_execution_timeout = 30000
let g:geode_max_rows = 1000
let g:geode_auto_commit = 0

" Formatting settings
let g:geode_format_on_save = 1
let g:geode_keyword_case = 'UPPER'
let g:geode_indent_size = 2

" Results display
let g:geode_results_position = 'bottom'  " bottom, right, tab
let g:geode_results_height = 15
let g:geode_results_width = 80

" UI options
let g:geode_show_line_numbers = 1
let g:geode_highlight_current_row = 1

GQL File Settings

Create filetype-specific settings:

" ~/.vim/ftplugin/gql.vim

" Indentation
setlocal tabstop=2
setlocal shiftwidth=2
setlocal softtabstop=2
setlocal expandtab
setlocal smartindent

" Text width for formatting
setlocal textwidth=100

" Enable omni-completion
setlocal omnifunc=geode#Complete

" Folding (fold on query boundaries)
setlocal foldmethod=syntax
setlocal foldlevel=99

" Comments
setlocal commentstring=--\ %s

" Key mappings (buffer local)
nnoremap <buffer> <leader>ge :GeodeExecute<CR>
nnoremap <buffer> <leader>gx :GeodeExplain<CR>
nnoremap <buffer> <leader>gp :GeodeProfile<CR>
nnoremap <buffer> <leader>gf :GeodeFormat<CR>
nnoremap <buffer> <leader>gc :GeodeConnect<CR>
nnoremap <buffer> <leader>gr :GeodeToggleResults<CR>

vnoremap <buffer> <leader>ge :GeodeExecuteSelection<CR>

" Execute with Ctrl+Enter
nnoremap <buffer> <C-CR> :GeodeExecute<CR>
inoremap <buffer> <C-CR> <Esc>:GeodeExecute<CR>
vnoremap <buffer> <C-CR> :GeodeExecuteSelection<CR>

Connection Profiles

Define multiple connection profiles:

" ~/.vimrc

" Connection profiles
let g:geode_connections = {
  \ 'development': {
  \   'host': 'localhost',
  \   'port': 3141,
  \   'database': 'dev',
  \ },
  \ 'staging': {
  \   'host': 'staging.example.com',
  \   'port': 3141,
  \   'database': 'staging',
  \   'tls': 1,
  \ },
  \ 'production': {
  \   'host': 'prod.example.com',
  \   'port': 3141,
  \   'database': 'production',
  \   'tls': 1,
  \   'read_only': 1,
  \ },
  \ }

" Default connection
let g:geode_default_connection = 'development'

" Quick connection switching
nnoremap <leader>c1 :GeodeUseConnection development<CR>
nnoremap <leader>c2 :GeodeUseConnection staging<CR>
nnoremap <leader>c3 :GeodeUseConnection production<CR>

Features

Syntax Highlighting

The plugin provides comprehensive syntax highlighting:

" ~/.vim/syntax/gql.vim (excerpt)

" Keywords
syntax keyword gqlKeyword MATCH WHERE RETURN CREATE DELETE MERGE SET REMOVE
syntax keyword gqlKeyword WITH UNWIND CALL YIELD ORDER BY SKIP LIMIT
syntax keyword gqlKeyword AND OR NOT IN IS NULL TRUE FALSE
syntax keyword gqlKeyword CASE WHEN THEN ELSE END
syntax keyword gqlKeyword OPTIONAL DISTINCT AS
syntax keyword gqlKeyword BEGIN COMMIT ROLLBACK TRANSACTION

" Functions
syntax keyword gqlFunction COUNT SUM AVG MIN MAX
syntax keyword gqlFunction COLLECT REDUCE
syntax keyword gqlFunction UPPER LOWER TRIM SUBSTRING REPLACE
syntax keyword gqlFunction SIZE LENGTH TYPE ID LABELS PROPERTIES
syntax keyword gqlFunction COALESCE NULLIF
syntax keyword gqlFunction DATE DATETIME TIME DURATION

" Types
syntax keyword gqlType Integer Float String Boolean Date DateTime Duration
syntax keyword gqlType Point List Map Node Relationship Path

" Labels (:Label)
syntax match gqlLabel ":\w\+"

" Properties (.property)
syntax match gqlProperty "\.\w\+"

" Variables
syntax match gqlVariable "\$\w\+"

" Strings
syntax region gqlString start=/"/ skip=/\\"/ end=/"/
syntax region gqlString start=/'/ skip=/\\'/ end=/'/

" Comments
syntax match gqlComment "--.*$"
syntax region gqlComment start="/\*" end="\*/"

" Numbers
syntax match gqlNumber "\<\d\+\>"
syntax match gqlNumber "\<\d\+\.\d\+\>"

" Highlighting groups
highlight link gqlKeyword Keyword
highlight link gqlFunction Function
highlight link gqlType Type
highlight link gqlLabel Structure
highlight link gqlProperty Identifier
highlight link gqlVariable Special
highlight link gqlString String
highlight link gqlComment Comment
highlight link gqlNumber Number

Omni-Completion

Intelligent completion via <C-x><C-o>:

" Completion function in autoload/geode.vim
function! geode#Complete(findstart, base)
  if a:findstart
    " Find start of word
    let line = getline('.')
    let start = col('.') - 1
    while start > 0 && line[start - 1] =~ '\w'
      let start -= 1
    endwhile
    return start
  else
    " Get completions from LSP or cache
    let completions = []

    " Context-aware completions
    let context = geode#GetContext()

    if context.after_colon
      " Complete labels
      let completions = geode#GetLabels(a:base)
    elseif context.after_dot
      " Complete properties
      let completions = geode#GetProperties(context.label, a:base)
    elseif context.after_bracket
      " Complete relationship types
      let completions = geode#GetRelationships(a:base)
    else
      " Complete keywords and functions
      let completions = geode#GetKeywordsAndFunctions(a:base)
    endif

    return completions
  endif
endfunction

Using Completion:

" In insert mode:
" <C-x><C-o>  - Trigger omni-completion
" <C-n>       - Next completion item
" <C-p>       - Previous completion item
" <C-y>       - Accept completion
" <C-e>       - Cancel completion

Query Execution

Execute queries directly from Vim:

" Execute entire buffer
:GeodeExecute

" Execute current query (query under cursor)
:GeodeExecuteCurrent

" Execute visual selection
:'<,'>GeodeExecuteSelection

" Execute with EXPLAIN
:GeodeExplain

" Execute with PROFILE
:GeodeProfile

" Cancel running query
:GeodeCancel

Results Window:

┌─ Geode Results ──────────────────────────────────────────┐
│ Query: MATCH (u:User) WHERE u.active = true RETURN u... │
│ Status: Success | Time: 12ms | Rows: 3                   │
├──────────────────────────────────────────────────────────┤
│ name          │ email                │ created_at        │
├───────────────┼──────────────────────┼───────────────────┤
│ Alice Johnson │ [email protected]    │ 2024-01-15        │
│ Bob Smith     │ [email protected]      │ 2024-02-20        │
│ Carol Davis   │ [email protected]    │ 2024-03-10        │
├──────────────────────────────────────────────────────────┤
│ [j/k] navigate | [y] yank row | [Y] yank all | [q] close │
└──────────────────────────────────────────────────────────┘

Results Window Keys:

KeyAction
j / kNavigate rows
h / lScroll horizontally
gg / GGo to first/last row
yYank current row
YYank all results
/Search results
n / NNext/previous search match
qClose results window
<CR>Expand row details

Code Formatting

Format GQL queries for consistency:

" Format entire buffer
:GeodeFormat

" Format visual selection
:'<,'>GeodeFormatSelection

" Format on save (if enabled)
autocmd BufWritePre *.gql :GeodeFormat

Before/After Formatting:

" Before
match(u:User)-[:FOLLOWS]->(f) where u.active=true and u.created>'2024-01-01' return f.name,count(*) as cnt group by f.name order by cnt desc limit 10;

" After
MATCH (u:User)-[:FOLLOWS]->(f)
WHERE u.active = true
  AND u.created > '2024-01-01'
RETURN f.name, COUNT(*) AS cnt
GROUP BY f.name
ORDER BY cnt DESC
LIMIT 10;

ALE Integration

Integrate with ALE for asynchronous linting:

" ~/.vimrc

" Enable ALE for GQL
let g:ale_linters = {
  \ 'gql': ['geode'],
  \ }

" ALE Geode linter settings
let g:ale_gql_geode_executable = 'geode'
let g:ale_gql_geode_options = 'lint --format=ale'

" ALE display settings
let g:ale_sign_error = '✗'
let g:ale_sign_warning = '⚠'
let g:ale_echo_msg_error_str = 'E'
let g:ale_echo_msg_warning_str = 'W'
let g:ale_echo_msg_format = '[%linter%] %s [%severity%]'

" Navigate errors
nmap <silent> [e <Plug>(ale_previous_wrap)
nmap <silent> ]e <Plug>(ale_next_wrap)

" Fix on save
let g:ale_fix_on_save = 1
let g:ale_fixers = {
  \ 'gql': ['geode_format'],
  \ }

Error Display:

  1 │ MATCH (u:User)
  2 │ WHER u.email = '[email protected]'
    │ ✗ E: Unknown keyword 'WHER'. Did you mean 'WHERE'?
  3 │ RETURN u;

vim-lsp Integration

For LSP support in classic Vim:

" ~/.vimrc

" Register Geode LSP
if executable('geode')
  au User lsp_setup call lsp#register_server({
    \ 'name': 'geode-gql',
    \ 'cmd': {server_info->['geode', 'lsp', '--stdio']},
    \ 'allowlist': ['gql'],
    \ })
endif

" LSP keymaps
function! s:on_lsp_buffer_enabled() abort
  setlocal omnifunc=lsp#complete
  setlocal signcolumn=yes

  nmap <buffer> gd <plug>(lsp-definition)
  nmap <buffer> gr <plug>(lsp-references)
  nmap <buffer> K <plug>(lsp-hover)
  nmap <buffer> <leader>rn <plug>(lsp-rename)
  nmap <buffer> [g <plug>(lsp-previous-diagnostic)
  nmap <buffer> ]g <plug>(lsp-next-diagnostic)
endfunction

augroup lsp_install
  au!
  autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END

asyncomplete Integration

Async completion for better performance:

" ~/.vimrc

" Register Geode source
au User asyncomplete_setup call asyncomplete#register_source(
  \ asyncomplete#sources#geode#get_source_options({
  \   'name': 'geode',
  \   'allowlist': ['gql'],
  \   'completor': function('asyncomplete#sources#geode#completor'),
  \ }))

" Tab completion
inoremap <expr> <Tab>   pumvisible() ? "\<C-n>" : "\<Tab>"
inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"
inoremap <expr> <CR>    pumvisible() ? "\<C-y>" : "\<CR>"

Key Mappings

Default Mappings

ActionMappingModeDescription
Execute Query<leader>genExecute query under cursor
Execute Selection<leader>gevExecute selected text
Explain Query<leader>gxnShow execution plan
Profile Query<leader>gpnExecute with profiling
Format Buffer<leader>gfnFormat entire buffer
Toggle Results<leader>grnToggle results window
Connect<leader>gcnConnect to server
Schema Explorer<leader>gSnOpen schema browser
Next Error]enGo to next error
Prev Error[enGo to previous error

Custom Mappings

" ~/.vim/ftplugin/gql.vim

" Quick execute with Ctrl+Enter
nnoremap <buffer> <C-CR> :GeodeExecute<CR>
inoremap <buffer> <C-CR> <Esc>:GeodeExecute<CR>a
vnoremap <buffer> <C-CR> :GeodeExecuteSelection<CR>

" Execute and stay in insert mode
inoremap <buffer> <C-S-CR> <C-o>:GeodeExecute<CR>

" Format with = (like other languages)
nnoremap <buffer> = :GeodeFormat<CR>

" Quick explain
nnoremap <buffer> <leader>e :GeodeExplain<CR>

" Quick profile
nnoremap <buffer> <leader>p :GeodeProfile<CR>

" Navigate queries in buffer
nnoremap <buffer> [q :GeodePrevQuery<CR>
nnoremap <buffer> ]q :GeodeNextQuery<CR>

" Copy results to clipboard
nnoremap <buffer> <leader>yy :GeodeYankResults<CR>
nnoremap <buffer> <leader>yj :GeodeYankResultsJSON<CR>

" Insert common patterns
inoremap <buffer> <C-g>m MATCH ()<Left>
inoremap <buffer> <C-g>w WHERE
inoremap <buffer> <C-g>r RETURN
inoremap <buffer> <C-g>c CREATE ()<Left>

Snippets

UltiSnips Integration

" ~/.vim/UltiSnips/gql.snippets

# Basic MATCH query
snippet match "MATCH query" b
MATCH (${1:n}:${2:Label})
WHERE ${3:condition}
RETURN ${4:$1};
endsnippet

# CREATE node
snippet create "CREATE node" b
CREATE (${1:n}:${2:Label} {
  ${3:property}: ${4:value}
});
endsnippet

# CREATE relationship
snippet rel "CREATE relationship" b
MATCH (a:${1:Label} {${2:prop}: ${3:value}})
MATCH (b:${4:Label} {${5:prop}: ${6:value}})
CREATE (a)-[:${7:REL_TYPE} {${8:}}]->(b);
endsnippet

# MERGE pattern
snippet merge "MERGE pattern" b
MERGE (${1:n}:${2:Label} {${3:id}: ${4:value}})
ON CREATE SET ${5:$1.created_at = datetime()}
ON MATCH SET ${6:$1.updated_at = datetime()}
RETURN $1;
endsnippet

# Path query
snippet path "Path query" b
MATCH path = (${1:start}:${2:Label})-[:${3:REL}*${4:1..3}]->(${5:end}:${6:Label})
WHERE ${7:condition}
RETURN path;
endsnippet

# Aggregation
snippet agg "Aggregation query" b
MATCH (${1:n}:${2:Label})
WHERE ${3:condition}
RETURN ${4:n.property}, ${5:COUNT}(*) AS ${6:count}
GROUP BY $4
ORDER BY $6 DESC
LIMIT ${7:10};
endsnippet

# Transaction block
snippet tx "Transaction block" b
BEGIN TRANSACTION;

${1:-- Your GQL statements here}

COMMIT;
endsnippet

# CASE expression
snippet case "CASE expression" b
CASE
  WHEN ${1:condition} THEN ${2:result}
  WHEN ${3:condition} THEN ${4:result}
  ELSE ${5:default}
END
endsnippet

SnipMate Snippets

# ~/.vim/snippets/gql.snippets

snippet match
	MATCH (${1:n}:${2:Label})
	WHERE ${3:condition}
	RETURN ${4:$1};

snippet create
	CREATE (${1:n}:${2:Label} {
	  ${3:property}: ${4:value}
	});

snippet path
	MATCH path = (${1:start}:${2:Label})-[:${3:REL}*${4:1..3}]->(${5:end}:${6:Label})
	RETURN path;

Commands Reference

Execution Commands

CommandDescription
:GeodeExecuteExecute query under cursor or entire buffer
:GeodeExecuteSelectionExecute visual selection
:GeodeExecuteCurrentExecute current query only
:GeodeExplainShow execution plan
:GeodeProfileExecute with profiling
:GeodeCancelCancel running query

Connection Commands

CommandDescription
:GeodeConnectConnect to server
:GeodeDisconnectDisconnect from server
:GeodeReconnectReconnect to server
:GeodeUseConnection {name}Switch to named connection
:GeodeUseDatabase {name}Switch database
:GeodeConnectionStatusShow connection status

Formatting Commands

CommandDescription
:GeodeFormatFormat entire buffer
:GeodeFormatSelectionFormat visual selection
:GeodeSetIndent {n}Set indent size
:GeodeSetKeywordCase {case}Set keyword case (UPPER/lower)

Schema Commands

CommandDescription
:GeodeSchemaOpen schema explorer
:GeodeLabelsList all labels
:GeodeRelationshipsList relationship types
:GeodeProperties {label}List properties for label
:GeodeIndexesList all indexes
:GeodeRefreshSchemaRefresh schema cache

Results Commands

CommandDescription
:GeodeToggleResultsToggle results window
:GeodeShowResultsShow results window
:GeodeHideResultsHide results window
:GeodeYankResultsCopy results to clipboard
:GeodeYankResultsJSONCopy results as JSON
:GeodeExportResults {file}Export to file

Troubleshooting

Common Issues

Syntax Highlighting Not Working:

" Check filetype detection
:set ft?
" Should show: filetype=gql

" Force filetype
:set ft=gql

" Check syntax loaded
:syntax list

Completion Not Working:

" Check omnifunc
:set omnifunc?
" Should show: omnifunc=geode#Complete

" Test completion manually
:call geode#Complete(0, '')

Connection Failures:

" Check connection status
:GeodeConnectionStatus

" Test connection
:GeodeTestConnection

" View connection log
:GeodeConnectionLog

Plugin Not Loading:

" Check plugin loaded
:scriptnames | grep geode

" Check for errors
:messages

" Reload plugin
:source ~/.vim/plugged/vim-geode-gql/plugin/geode.vim

Debug Mode

Enable debug logging:

" Enable debug mode
let g:geode_debug = 1
let g:geode_log_file = '/tmp/geode-vim.log'

" View debug log
:edit /tmp/geode-vim.log

" Or tail in terminal
:terminal tail -f /tmp/geode-vim.log

Best Practices

Organize Query Files: Keep .gql files in version control for collaboration.

Use Connection Profiles: Define profiles for different environments.

Enable Format on Save: Ensure consistent code style.

Leverage Snippets: Create snippets for common query patterns.

Use ALE for Linting: Get real-time feedback on syntax errors.

Set Up Key Mappings: Create efficient mappings for frequent operations.

Profile Complex Queries: Always profile before running expensive queries.

Use Read-Only for Production: Mark production connections as read-only.

Check Schema Before Writing: Use schema explorer to verify labels and properties.

Document Custom Mappings: Comment your mappings for future reference.

Further Reading

  • Vim Script Documentation
  • ALE Documentation
  • vim-lsp Configuration Guide
  • UltiSnips Documentation
  • Vim Plugin Development Guide
  • Modal Editing Best Practices

Related Articles