From 97564e22a4ee7615efcdad7b0c574518769bb6a8 Mon Sep 17 00:00:00 2001 From: Jules Aguillon Date: Fri, 19 Apr 2024 15:53:14 +0200 Subject: [PATCH 1/7] Vim: Insert Python path at the front This avoids loading a different version of the plugin or loading the installed plugin while developing locally. --- vim/merlin/autoload/merlin.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vim/merlin/autoload/merlin.vim b/vim/merlin/autoload/merlin.vim index f78424cdd1..4cd6462fe8 100644 --- a/vim/merlin/autoload/merlin.vim +++ b/vim/merlin/autoload/merlin.vim @@ -81,7 +81,7 @@ endif let s:current_dir=expand(":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 From 070d8d4276b53a80d0d5f98253bc57e357f4dc8f Mon Sep 17 00:00:00 2001 From: Jules Aguillon Date: Fri, 19 Apr 2024 16:23:49 +0200 Subject: [PATCH 2/7] Vim: Support project wide occurrences The new command :MerlinOccurrencesProjectWide acts similarly to :MerlinOccurrences except that: - It uses project-wide occurrences. - It uses the quickfix list instead of the location list. - It doesn't show text extract in the list. --- vim/merlin/autoload/merlin.py | 37 +++++++++++++++++++++------------- vim/merlin/autoload/merlin.vim | 18 ++++++++++++++++- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/vim/merlin/autoload/merlin.py b/vim/merlin/autoload/merlin.py index 41c1aab9e3..f31a742f05 100644 --- a/vim/merlin/autoload/merlin.py +++ b/vim/merlin/autoload/merlin.py @@ -323,9 +323,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: @@ -480,29 +481,36 @@ def vim_document_under_cursor(): vim_document_at_cursor(None) # 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)) + lnum = pos['start']['line'] + lcol = pos['start']['col'] + if 'file' in pos: + text = "" + is_current_file = (pos['file'] == cur_fname) + else: + text = vim.current.buffer[lnum - 1] + text = text.replace("'", "''") + is_current_file = True + if is_current_file and (lnum, lcol) <= (line, col): cursorpos = nr + bufnr_or_fname = "'filename':'%s'" % pos['file'] if 'file' in pos else "'bufnr':%d" % bufnr + vim.command("let l:tmp = {%s,'lnum':%d,'col':%d,'vcol':0,'nr':%d,'pattern':'','text':'%s','type':'I','valid':1}" % + (bufnr_or_fname, lnum, lcol + 1, nr, text)) nr = nr + 1 vim.command("call add(%s, l:tmp)" % vimvar) 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 @@ -521,8 +529,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']: diff --git a/vim/merlin/autoload/merlin.vim b/vim/merlin/autoload/merlin.vim index 4cd6462fe8..0ba5fc5fa9 100644 --- a/vim/merlin/autoload/merlin.vim +++ b/vim/merlin/autoload/merlin.vim @@ -481,7 +481,7 @@ 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 @@ -494,6 +494,20 @@ function! merlin#Occurrences() 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) + execute ":cc! " . l:pos + if g:merlin_display_occurrence_list + copen + endif +endfunction + function! merlin#OccurrencesRename(text) MerlinPy merlin.vim_occurrences_replace(vim.eval("a:text")) endfunction @@ -749,6 +763,8 @@ function! merlin#Register() command! -buffer -nargs=0 MerlinOccurrences call merlin#Occurrences() nmap (MerlinSearchOccurrencesForward) :call merlin_find#OccurrencesSearch('/'):let v:searchforward=1 nmap (MerlinSearchOccurrencesBackward) :call merlin_find#OccurrencesSearch('?'):let v:searchforward=0 + " Project-wide occurrences + command! -buffer -nargs=0 MerlinOccurrencesProjectWide call merlin#OccurrencesProjectWide() " Rename command! -buffer -nargs=* MerlinRename call merlin#OccurrencesRename() From aa54b9f3a71a48c658bb88d9476df3cd469970ee Mon Sep 17 00:00:00 2001 From: Jules Aguillon Date: Mon, 22 Apr 2024 23:48:47 +0200 Subject: [PATCH 3/7] Vim: Refactor: Separate vim record formatting from other computations. This function is likely to be reused later. --- vim/merlin/autoload/merlin.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/vim/merlin/autoload/merlin.py b/vim/merlin/autoload/merlin.py index f31a742f05..a55fbb66f5 100644 --- a/vim/merlin/autoload/merlin.py +++ b/vim/merlin/autoload/merlin.py @@ -84,6 +84,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(): @@ -492,19 +507,20 @@ def vim_occurrences(vimvar, project_wide): for pos in lst: lnum = pos['start']['line'] lcol = pos['start']['col'] + occur = { "lnum": lnum, "col": lcol + 1, "vcol": 0, "nr": nr, + "pattern": "", "type": "I", "valid": 1 } if 'file' in pos: - text = "" + occur["filename"] = pos['file'] + occur["text"] = "" is_current_file = (pos['file'] == cur_fname) else: - text = vim.current.buffer[lnum - 1] - text = text.replace("'", "''") + occur["bufnr"] = bufnr + occur["text"] = vim.current.buffer[lnum - 1] is_current_file = True + vim.command("let l:tmp = " + vim_record(occur)) + vim.command("call add(%s, l:tmp)" % vimvar) if is_current_file and (lnum, lcol) <= (line, col): cursorpos = nr - bufnr_or_fname = "'filename':'%s'" % pos['file'] if 'file' in pos else "'bufnr':%d" % bufnr - vim.command("let l:tmp = {%s,'lnum':%d,'col':%d,'vcol':0,'nr':%d,'pattern':'','text':'%s','type':'I','valid':1}" % - (bufnr_or_fname, lnum, lcol + 1, nr, text)) nr = nr + 1 - vim.command("call add(%s, l:tmp)" % vimvar) return cursorpos + 1 def vim_occurrences_search(): From 5ae2824165adc3acd47a7f93b54db88684df8620 Mon Sep 17 00:00:00 2001 From: Jules Aguillon Date: Tue, 23 Apr 2024 11:59:58 +0200 Subject: [PATCH 4/7] Vim: Occurrences: Text preview in the current file --- vim/merlin/autoload/merlin.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/vim/merlin/autoload/merlin.py b/vim/merlin/autoload/merlin.py index a55fbb66f5..bf1255a247 100644 --- a/vim/merlin/autoload/merlin.py +++ b/vim/merlin/autoload/merlin.py @@ -509,14 +509,13 @@ def vim_occurrences(vimvar, project_wide): lcol = pos['start']['col'] occur = { "lnum": lnum, "col": lcol + 1, "vcol": 0, "nr": nr, "pattern": "", "type": "I", "valid": 1 } - if 'file' in pos: - occur["filename"] = pos['file'] - occur["text"] = "" - is_current_file = (pos['file'] == cur_fname) - else: - occur["bufnr"] = bufnr + is_current_file = ('file' not in pos or pos['file'] == cur_fname) + if is_current_file: occur["text"] = vim.current.buffer[lnum - 1] - is_current_file = True + occur["bufnr"] = bufnr + else: + occur["text"] = "" + occur["filename"] = pos['file'] vim.command("let l:tmp = " + vim_record(occur)) vim.command("call add(%s, l:tmp)" % vimvar) if is_current_file and (lnum, lcol) <= (line, col): cursorpos = nr From 308e17d4fdd6217e00c5c8899e4fa99e5735428d Mon Sep 17 00:00:00 2001 From: Jules Aguillon Date: Tue, 23 Apr 2024 13:41:59 +0200 Subject: [PATCH 5/7] Vim: Occurrences: Don't crash on incomplete result --- vim/merlin/autoload/merlin.vim | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vim/merlin/autoload/merlin.vim b/vim/merlin/autoload/merlin.vim index 0ba5fc5fa9..af99c4dfb4 100644 --- a/vim/merlin/autoload/merlin.vim +++ b/vim/merlin/autoload/merlin.vim @@ -488,7 +488,9 @@ function! merlin#Occurrences() 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 @@ -502,7 +504,9 @@ function! merlin#OccurrencesProjectWide() return endif call setqflist(l:occurrences) - execute ":cc! " . l:pos + if l:pos > 0 + execute ":cc! " . l:pos + endif if g:merlin_display_occurrence_list copen endif From cc33be2534982953f6a7bc29733d49b8db843ea3 Mon Sep 17 00:00:00 2001 From: Jules Aguillon Date: Tue, 23 Apr 2024 13:42:55 +0200 Subject: [PATCH 6/7] Vim: Occurrences: Add text previews Files containing occurrences are read in a first step and lines containing an occurrence are kept in memory. Vim potentially has open buffers for some of these files but this couldn't be used reliably as unused buffers would be emptied of their content. We don't open new buffers as that could trigger expensive function in plugins and would use more memory. --- vim/merlin/autoload/merlin.py | 53 ++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/vim/merlin/autoload/merlin.py b/vim/merlin/autoload/merlin.py index bf1255a247..cb447bbe76 100644 --- a/vim/merlin/autoload/merlin.py +++ b/vim/merlin/autoload/merlin.py @@ -4,6 +4,7 @@ import re import os import sys +import itertools from sys import platform enclosing_types = [] # nothing to see here @@ -495,6 +496,46 @@ 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, project_wide): vim.command("let %s = []" % vimvar) @@ -504,21 +545,19 @@ def vim_occurrences(vimvar, project_wide): cur_fname = vim.current.buffer.name nr = 0 cursorpos = 0 - for pos in lst: + 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 } - is_current_file = ('file' not in pos or pos['file'] == cur_fname) - if is_current_file: - occur["text"] = vim.current.buffer[lnum - 1] + "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["text"] = "" occur["filename"] = pos['file'] vim.command("let l:tmp = " + vim_record(occur)) vim.command("call add(%s, l:tmp)" % vimvar) - if is_current_file and (lnum, lcol) <= (line, col): cursorpos = nr nr = nr + 1 return cursorpos + 1 From db463ee70b95902b3a14a8cda5f81397bda34752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulysse=20G=C3=A9rard?= Date: Tue, 4 Jun 2024 17:36:30 +0200 Subject: [PATCH 7/7] Add changelog entry for #1767 --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 12a07d786f..bc05c2bd13 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 ==========