Skip to content

Commit

Permalink
Merge pull request #1767 from Julow/vim_projectwideoccurrences_m
Browse files Browse the repository at this point in the history
Vim: Project-wide Occurrences
  • Loading branch information
voodoos committed Jun 4, 2024
2 parents 0dff10e + db463ee commit 9e8a1e1
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ UNRELEASED
the correct name of the current unit in the presence of wrapping (#1776)
+ editor modes
- emacs: add basic support for project-wide occurrences (#1766)
- vim: add basic support for project-wide occurrences (#1767, @Julow)

merlin 5.0
==========
Expand Down
95 changes: 79 additions & 16 deletions vim/merlin/autoload/merlin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import re
import os
import sys
import itertools
from sys import platform

enclosing_types = [] # nothing to see here
Expand Down Expand Up @@ -84,6 +85,21 @@ def catch_and_print(f, msg=None):
def concat_map(f, args):
return [item for arg in args for item in f(arg)]

# Format an integer or a string into a form that can be parsed back by Vim.
def vim_value(v):
if isinstance(v, int):
return str(v)
if isinstance(v, str):
return "'%s'" % v.replace("'", "''")
raise Exception("Failed to convert into a vim value: %s" % str(v))

# Format a dictionnary containing integer and string values into a Vim record.
def vim_record(d):
def vim_field(f):
key, val = f
return "'%s':%s" % (key, vim_value(val))
return "{" + ",".join(map(vim_field, d.items())) + "}"

######## PROCESS MANAGEMENT

def current_context():
Expand Down Expand Up @@ -323,9 +339,10 @@ def command_motion(cmd, target, pos):
except MerlinExc as e:
try_print_error(e)

def command_occurrences(pos):
def command_occurrences(pos, project_wide):
try:
lst_or_err = command("occurrences", "-identifier-at", fmtpos(pos))
scope_args = ["-scope", "project"] if project_wide else []
lst_or_err = command("occurrences", "-identifier-at", fmtpos(pos), *scope_args)
if not isinstance(lst_or_err, list):
print(lst_or_err)
else:
Expand Down Expand Up @@ -479,30 +496,75 @@ def vim_document_at_cursor(path):
def vim_document_under_cursor():
vim_document_at_cursor(None)

# Read the lines numbers present in [lines] from file [fname]. End-of-line is
# stripped from each line.
def read_lines_of_file(fname, lines):
lines = iter(sorted(set(lines)))
next_line = next(lines)
n = 1
r = {}
try:
try:
with open(fname) as inp:
for line in inp:
if next_line == n:
r[n] = line.rstrip("\n")
next_line = next(lines)
n += 1
except FileNotFoundError:
pass
# File has been truncated or not found
while True:
r[next_line] = ""
next_line = next(lines)
except StopIteration:
return r

# From the result of [command_occurrences], read the start line for each
# results. Generate the same occurrences with the 'text' field added.
def with_text_previews(occurs):
preview_lines_by_file = {
fname: read_lines_of_file(fname, [ oc['start']['line'] for oc in occurs ])
for fname, occurs
in itertools.groupby(occurs, lambda oc: oc.get('file'))
if fname != None }
for oc in occurs:
lnum = oc['start']['line']
if 'file' not in oc: # Current buffer
text = vim.current.buffer[lnum - 1]
else:
text = preview_lines_by_file[oc['file']][lnum]
yield { "text": text, **oc }

# Occurrences
def vim_occurrences(vimvar):
def vim_occurrences(vimvar, project_wide):
vim.command("let %s = []" % vimvar)
line, col = vim.current.window.cursor
lst = command_occurrences((line, col))
lst = map(lambda x: x['start'], lst)
lst = command_occurrences((line, col), project_wide)
bufnr = vim.current.buffer.number
cur_fname = vim.current.buffer.name
nr = 0
cursorpos = 0
for pos in lst:
lnum = pos['line']
lcol = pos['col']
if (lnum, lcol) <= (line, col): cursorpos = nr
text = vim.current.buffer[lnum - 1]
text = text.replace("'", "''")
vim.command("let l:tmp = {'bufnr':%d,'lnum':%d,'col':%d,'vcol':0,'nr':%d,'pattern':'','text':'%s','type':'I','valid':1}" %
(bufnr, lnum, lcol + 1, nr, text))
nr = nr + 1
for pos in with_text_previews(lst):
lnum = pos['start']['line']
lcol = pos['start']['col']
occur = { "lnum": lnum, "col": lcol + 1, "vcol": 0, "nr": nr,
"pattern": "", "type": "I", "valid": 1 , "text": pos['text'] }
if 'file' not in pos or pos['file'] == cur_fname:
# Occurrence is in the current buffer
if (lnum, lcol) <= (line, col): cursorpos = nr
occur["bufnr"] = bufnr
else:
occur["filename"] = pos['file']
vim.command("let l:tmp = " + vim_record(occur))
vim.command("call add(%s, l:tmp)" % vimvar)
nr = nr + 1
return cursorpos + 1

def vim_occurrences_search():
project_wide = False
line, col = vim.current.window.cursor
lst = command_occurrences((line, col))
lst = command_occurrences((line, col), project_wide)
result = ""
over = ""
start_col = 0
Expand All @@ -521,8 +583,9 @@ def vim_occurrences_search():
return "[%s, '%s', '%s']" % (start_col, over, result)

def vim_occurrences_replace(content):
project_wide = False
cursor = vim.current.window.cursor
lst = command_occurrences(cursor)
lst = command_occurrences(cursor, project_wide)
lst.reverse()
for pos in lst:
if pos['start']['line'] == pos['end']['line']:
Expand Down
26 changes: 23 additions & 3 deletions vim/merlin/autoload/merlin.vim
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ endif
let s:current_dir=expand("<sfile>:p:h")
silent! MerlinPy import sys, vim
MerlinPy if not vim.eval("s:current_dir") in sys.path:
\ sys.path.append(vim.eval("s:current_dir"))
\ sys.path.insert(0, vim.eval("s:current_dir"))

MerlinPy import merlin

Expand Down Expand Up @@ -481,19 +481,37 @@ endfunction
function! merlin#Occurrences()
let l:occurrences = []
let l:pos = 0
MerlinPy vim.command ("let l:pos = %d" % merlin.vim_occurrences("l:occurrences"))
MerlinPy vim.command ("let l:pos = %d" % merlin.vim_occurrences("l:occurrences", False))

if l:occurrences == []
return
endif

call setloclist(0, l:occurrences)
execute ":ll! " . l:pos
if l:pos > 0
execute ":ll! " . l:pos
endif
if g:merlin_display_occurrence_list
lopen
endif
endfunction

function! merlin#OccurrencesProjectWide()
let l:occurrences = []
let l:pos = 0
MerlinPy vim.command ("let l:pos = %d" % merlin.vim_occurrences("l:occurrences", True))
if l:occurrences == []
return
endif
call setqflist(l:occurrences)
if l:pos > 0
execute ":cc! " . l:pos
endif
if g:merlin_display_occurrence_list
copen
endif
endfunction

function! merlin#OccurrencesRename(text)
MerlinPy merlin.vim_occurrences_replace(vim.eval("a:text"))
endfunction
Expand Down Expand Up @@ -749,6 +767,8 @@ function! merlin#Register()
command! -buffer -nargs=0 MerlinOccurrences call merlin#Occurrences()
nmap <silent><buffer> <Plug>(MerlinSearchOccurrencesForward) :call merlin_find#OccurrencesSearch('/')<cr>:let v:searchforward=1<cr>
nmap <silent><buffer> <Plug>(MerlinSearchOccurrencesBackward) :call merlin_find#OccurrencesSearch('?')<cr>:let v:searchforward=0<cr>
" Project-wide occurrences
command! -buffer -nargs=0 MerlinOccurrencesProjectWide call merlin#OccurrencesProjectWide()

" Rename
command! -buffer -nargs=* MerlinRename call merlin#OccurrencesRename(<f-args>)
Expand Down

0 comments on commit 9e8a1e1

Please sign in to comment.