From f5329a8425f1268270415a9fd3a9e856c969d5f9 Mon Sep 17 00:00:00 2001 From: matsb Date: Tue, 26 Feb 2019 20:38:42 +0000 Subject: [PATCH 1/2] Changes to improve data.table completion - Adds `ncm_r_dt` setting. When set, this changes the regular expression that's used to check whether the cursor is inside the brackets of a data.frame (or data.table). This causes completions to be offered before a comma is typed, which means column names can be completed in the 'i' component of a data.table command, which occurs before the first comma i.e. DT[i, j, by] - Changes completion behaviour so that matches are refreshed as the user types more. This means that function names are now offered as completions after the the first comma has been typed within a data.table command. - Adds test cases for the new data.table completion behaviour --- plugin/ncm_r.vim | 1 + pythonx/ncm_r.py | 4 ++-- pythonx/rlang.py | 9 +++++++-- pythonx/rsource.py | 1 + test/min_vimrc | 3 +++ test/test_ncmr.py | 11 +++++++++++ 6 files changed, 25 insertions(+), 4 deletions(-) diff --git a/plugin/ncm_r.vim b/plugin/ncm_r.vim index 4dc2c4e..181d0b8 100644 --- a/plugin/ncm_r.vim +++ b/plugin/ncm_r.vim @@ -10,3 +10,4 @@ let $NCM_R = 'TRUE' let g:ncm_r_column_layout = get(g:, 'ncm_r_column_layout', 1) let g:ncm_r_column1_length = get(g:, 'ncm_r_column1_length', 13) let g:ncm_r_column2_length = get(g:, 'ncm_r_column2_length', 11) +let g:ncm_r_dt = get(g:, 'ncm_r_dt', 0) diff --git a/pythonx/ncm_r.py b/pythonx/ncm_r.py index 9298054..233859d 100644 --- a/pythonx/ncm_r.py +++ b/pythonx/ncm_r.py @@ -282,7 +282,7 @@ def on_complete(self, ctx): return pipe = rlang.get_pipe(cur_buffer, lnum, col) - data = rlang.get_df_inside_brackets(ctx['typed']) + data = rlang.get_df_inside_brackets(ctx['typed'], dt_option = self._settings['dt']) self._info('word: "{}", func: "{}", pkg: {}, pipe: {}, data: {}'.format( word, func, pkg, pipe, data)) @@ -297,7 +297,7 @@ def on_complete(self, ctx): matches = self.get_matches(word, pkg=pkg) - self.complete(ctx, ctx['startccol'], matches) + self.complete(ctx, ctx['startccol'], matches, refresh = 1) SOURCE = Source(vim) diff --git a/pythonx/rlang.py b/pythonx/rlang.py index 1ce4ae9..de10897 100644 --- a/pythonx/rlang.py +++ b/pythonx/rlang.py @@ -159,13 +159,18 @@ def get_option(typed=''): return None -def get_df_inside_brackets(typed=''): +def get_df_inside_brackets(typed='', dt_option=0): """Return df name when cursor is inside brackets""" if not typed: return '' - df_brackets = re.compile(r'(\w+)\[[^\[\]]*,[^\[\]]*$') + # If the dt (data.table) option is set, then switch the regex so that it matches before a comma is typed + if dt_option: + df_brackets = re.compile(r'(\w+)\[[^\[\]]*$') + else: + df_brackets = re.compile(r'(\w+)\[[^\[\]]*,[^\[\]]*$') + df_match = df_brackets.search(typed) if df_match: diff --git a/pythonx/rsource.py b/pythonx/rsource.py index 85ab52a..341d181 100644 --- a/pythonx/rsource.py +++ b/pythonx/rsource.py @@ -28,6 +28,7 @@ def __init__(self, nvim): settings['col1_len'] = self.nvim.eval('g:ncm_r_column1_length') settings['col2_len'] = self.nvim.eval('g:ncm_r_column2_length') settings['col_layout'] = self.nvim.eval('g:ncm_r_column_layout') + settings['dt'] = self.nvim.eval('g:ncm_r_dt') settings['filetype'] = self.nvim.eval('&filetype') settings['nvimr_id'] = '' diff --git a/test/min_vimrc b/test/min_vimrc index 5755558..9fa84f6 100644 --- a/test/min_vimrc +++ b/test/min_vimrc @@ -16,6 +16,9 @@ call plug#end() autocmd BufEnter * call ncm2#enable_for_buffer() set completeopt=noinsert,menuone,noselect +" For data.table completion behaviour +let g:ncm_r_dt = 1 + " UltiSnips + NCM let g:UltiSnipsExpandTrigger = "(ultisnips_expand_or_jump)" let g:UltiSnipsJumpForwardTrigger = "(ultisnips_expand_or_jump)" diff --git a/test/test_ncmr.py b/test/test_ncmr.py index 5f5c6b3..efbf9e5 100644 --- a/test/test_ncmr.py +++ b/test/test_ncmr.py @@ -222,6 +222,17 @@ def feedkeys(keys): feedkeys(['$i']) TEST.ask() +# ==== DATA.TABLE ==== # +TEST = TestCase('Is ncm-R suggesting the sleep variable "extra" inside brackets and before a comma is typed?', + ['sleep[ext']) +NVIM.feedkeys('A') +TEST.ask() + +TEST = TestCase('Is ncm-R suggesting functions e.g. "median" inside brackets and after a comma is typed?', + ['sleep[, me']) +NVIM.feedkeys('A') +TEST.ask() + # ==== IT'S OVER ==== # TEST = TestCase(r'Testing is over \o/') TEST.ask() From c59fcf9088cb19a86ab03f95273083d409037a06 Mon Sep 17 00:00:00 2001 From: Matthew Burgess Date: Sun, 23 Jun 2019 18:22:32 +0100 Subject: [PATCH 2/2] Removes need for `g:ncm_r_dt` option `data.table` objects are detected automatically using the omni object match list. Also: - Adds support for completion when using `data.table`s with `magrittr` pipes, using `.` to refer to the 'parent' DT (note that as a side-effect, ordinary `data.frame`s can be used in the same way and completion work as expected) - In order to get this pipe behaviour to work, the regular expression for checking if completion is happening inside the brackets of a `data.frame` was changed to allow dots in the name of the DF. This could be considered a bug fix since previously completion inside brackets did not work as expected if the DF had dots in its name. --- plugin/ncm_r.vim | 1 - pythonx/ncm_r.py | 9 ++++++--- pythonx/rlang.py | 28 +++++++++++++++++++++------- pythonx/rsource.py | 1 - test/min_vimrc | 3 --- test/test_ncmr.py | 14 ++++++++++---- 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/plugin/ncm_r.vim b/plugin/ncm_r.vim index 181d0b8..4dc2c4e 100644 --- a/plugin/ncm_r.vim +++ b/plugin/ncm_r.vim @@ -10,4 +10,3 @@ let $NCM_R = 'TRUE' let g:ncm_r_column_layout = get(g:, 'ncm_r_column_layout', 1) let g:ncm_r_column1_length = get(g:, 'ncm_r_column1_length', 13) let g:ncm_r_column2_length = get(g:, 'ncm_r_column2_length', 11) -let g:ncm_r_dt = get(g:, 'ncm_r_dt', 0) diff --git a/pythonx/ncm_r.py b/pythonx/ncm_r.py index 233859d..1780b31 100644 --- a/pythonx/ncm_r.py +++ b/pythonx/ncm_r.py @@ -187,7 +187,6 @@ def get_matches(self, word, pkg=None, pipe=None, data=None): :returns: list of ncm matches """ - self.get_all_obj_matches() obj_m = self._obj_matches if pipe or data: @@ -281,8 +280,12 @@ def on_complete(self, ctx): if isinquot and func and not re.search('(library|require|data)', func): return + self.get_all_obj_matches() pipe = rlang.get_pipe(cur_buffer, lnum, col) - data = rlang.get_df_inside_brackets(ctx['typed'], dt_option = self._settings['dt']) + data = rlang.get_df_inside_brackets(ctx['typed'], obj_m=self._obj_matches) + + if pipe and data == '.': + data = pipe self._info('word: "{}", func: "{}", pkg: {}, pipe: {}, data: {}'.format( word, func, pkg, pipe, data)) @@ -297,7 +300,7 @@ def on_complete(self, ctx): matches = self.get_matches(word, pkg=pkg) - self.complete(ctx, ctx['startccol'], matches, refresh = 1) + self.complete(ctx, ctx['startccol'], matches, refresh=1) SOURCE = Source(vim) diff --git a/pythonx/rlang.py b/pythonx/rlang.py index de10897..c4987ba 100644 --- a/pythonx/rlang.py +++ b/pythonx/rlang.py @@ -159,21 +159,35 @@ def get_option(typed=''): return None -def get_df_inside_brackets(typed='', dt_option=0): +def get_df_inside_brackets(typed='', obj_m=[]): """Return df name when cursor is inside brackets""" if not typed: return '' - # If the dt (data.table) option is set, then switch the regex so that it matches before a comma is typed - if dt_option: - df_brackets = re.compile(r'(\w+)\[[^\[\]]*$') - else: - df_brackets = re.compile(r'(\w+)\[[^\[\]]*,[^\[\]]*$') + df_brackets = re.compile(r'([\w.]+)\[[^\[\]]*$') df_match = df_brackets.search(typed) if df_match: - return df_match.group(1) + full_match = df_match.group(0) + word = df_match.group(1) + + if ',' in full_match: + return word + + # Check if the object is a data.table or '.' (indicating it's referring + # to a data.frame or data.table being used in a pipe), if so then + # return it + if word == '.': + return word + + try: + match_struct = [m.get('struct', '') for m in obj_m if m.get('word', '') == word][0] + except IndexError: + match_struct = '' + + if match_struct == 'data.table': + return word return '' diff --git a/pythonx/rsource.py b/pythonx/rsource.py index 341d181..85ab52a 100644 --- a/pythonx/rsource.py +++ b/pythonx/rsource.py @@ -28,7 +28,6 @@ def __init__(self, nvim): settings['col1_len'] = self.nvim.eval('g:ncm_r_column1_length') settings['col2_len'] = self.nvim.eval('g:ncm_r_column2_length') settings['col_layout'] = self.nvim.eval('g:ncm_r_column_layout') - settings['dt'] = self.nvim.eval('g:ncm_r_dt') settings['filetype'] = self.nvim.eval('&filetype') settings['nvimr_id'] = '' diff --git a/test/min_vimrc b/test/min_vimrc index 9fa84f6..5755558 100644 --- a/test/min_vimrc +++ b/test/min_vimrc @@ -16,9 +16,6 @@ call plug#end() autocmd BufEnter * call ncm2#enable_for_buffer() set completeopt=noinsert,menuone,noselect -" For data.table completion behaviour -let g:ncm_r_dt = 1 - " UltiSnips + NCM let g:UltiSnipsExpandTrigger = "(ultisnips_expand_or_jump)" let g:UltiSnipsJumpForwardTrigger = "(ultisnips_expand_or_jump)" diff --git a/test/test_ncmr.py b/test/test_ncmr.py index efbf9e5..1bc463a 100644 --- a/test/test_ncmr.py +++ b/test/test_ncmr.py @@ -223,16 +223,22 @@ def feedkeys(keys): TEST.ask() # ==== DATA.TABLE ==== # -TEST = TestCase('Is ncm-R suggesting the sleep variable "extra" inside brackets and before a comma is typed?', - ['sleep[ext']) +send_rcmd("library('data.table'); sleep_dt <- as.data.table(sleep)") +TEST = TestCase('Is ncm-R suggesting median?', + ['sleep_dt[, medi']) NVIM.feedkeys('A') TEST.ask() -TEST = TestCase('Is ncm-R suggesting functions e.g. "median" inside brackets and after a comma is typed?', - ['sleep[, me']) +TEST = TestCase('Is ncm-R suggesting the sleep variable "extra"?', + ['sleep_dt[ext']) NVIM.feedkeys('A') TEST.ask() +TEST = TestCase('Is ncm-R suggesting the sleep variable "extra"?', + ['sleep_dt %>%', ' .[ext']) +NVIM.feedkeys(DOWN + 'A') +TEST.ask() + # ==== IT'S OVER ==== # TEST = TestCase(r'Testing is over \o/') TEST.ask()