vim-sandwich icon indicating copy to clipboard operation
vim-sandwich copied to clipboard

LaTeX Commands with several arguments in curly braces

Open kiryph opened this issue 8 years ago • 6 comments

I want to select following multicolumn command inside a latex table:

... & \multicolumn{2}{r}{\textbf{columns x and x+1}} & ...

% some further commands with additional arguments also in square brackets to make things even more fun
\chapter[toctitle with spaces]{title}
\newcommand*{\cmd}[nargs][optargdefault]{defn}

I have following textobjects defined:

omap <buffer> ic <Plug>(textobj-sandwich-query-i)c
xmap <buffer> ic <Plug>(textobj-sandwich-query-i)c
omap <buffer> ac <Plug>(textobj-sandwich-query-a)c
xmap <buffer> ac <Plug>(textobj-sandwich-query-a)c

For example if my cursor is on the first x. I would like to select the multicolumn with vacac. Right now, it does not do it.

As far as I can see following rule must be changed:

        \   {
        \     '__filetype__': 'initex',
        \     'buns'    : ['\\\a\+\*\?{', '}'],
        \     'filetype': ['initex', 'plaintex', 'tex'],
        \     'kind'    : ['delete', 'replace', 'auto', 'query'],
        \     'regex'   : 1,
        \     'nesting' : 1,
        \     'input'   : ['c'],
        \     'indentkeys-' : '{,},0{,0}',
        \   },

to something like

        \     'buns'    : ['\\\a\+\*\?\({[^{]*}\)*\(\[[^[]*\]\)*{', '}'],

I have added groups \({[^{]*}\)* and \(\[[^[]*\]\)*.

However, it does not work as expected. Do you know how to adjust the regex for this?

I guess this rule needs some assumption how these cases are written in latex. If someone doesn't follow these rules, the sandwich rule might not work for them.

kiryph avatar Mar 17 '17 13:03 kiryph

Yes I reproduced. It seems the regex pattern is correct.

Currently I'm using the built-in searchpairpos() function for searching nesting pair, but it seems it doesn't work as I expected for this pattern. So I will try to brash up the searching algorithm and check whether it works or not. Let me think.

machakann avatar Mar 18 '17 07:03 machakann

I also thought about this a little bit more. And latex does not make this easy. Consider following two cases:

\textcolor{red}{text in red} % single command with two mandatory arguments
\LaTeX{} {\color{red} text in red} % single command with no arguments but is
% followed up with empty braces so latex does not eat all whitespace behind the command
% and a scoped color switch

So it looks like it is not possible to have a general for this.

One idea is to keep the current definition for the simple generic cases ['\\\a\+\*\?{', '}'] and a dictionary which defines extended matching in the spirit of vimtexs folding of commands (g:vimtex_fold_commands) where also a general folding command is not feasible.

The dictionary would look like:

    let g:sandwich_tex_commands = {
        \ 'multicolumn' : 'regex or variant name',
        \ 'textcolor' : 'regex or variant name',
        \}

variant name would be something like single, double, triple referring how many arguments in curly braces this command has. Would it be possible to allow for square brackets in the general rule?

Anyhow, I am not sure whether it is worthwhile investing so much into this feature. I really like the idea of textobjects of vim and how your plugin extends it. However, drawing a line of the feature set is sometimes acceptable.

kiryph avatar Mar 18 '17 20:03 kiryph

I can confirm that LaTeX commands are not easy to parse. There is no general rule as to how many optional or normal arguments a command takes. As @kiryph says, vimtex defines folding for different types of commands, where the command types are specified through a regex.

Note, though, that I think that it is quite common to combine args without spaces, i.e., the following would be intuitivaly interpreted as a single command with 3 args:

\somecommand{...}{...}{...}

I think the most sensible thing to do is one of the following:

  1. Make the assumption that as long as there is no whitespace between the args they belong to the same command.
  2. Define different text objects for different commands with specified number of arguments.
  3. Do nothing, use the current behaviour.

Personally, I think number 1 and 3 are both sensible options. I think number 2 might not be worth the effort.

lervag avatar Mar 18 '17 21:03 lervag

@kiryph @lervag You're right. I realized that it is almost impossible to define a general rule addressing this case... Also I should say that the behavior of built-in searchpairpos() is reasonable... Probably this is a case somehow beyond simple regular expression pattern matching.


Alternatively, another plugin textobj-functioncall might be helpful for this case. This is a textobject to select a text like foo(bar, baz), braketed text with header (or footer). The tex command is not a function, so the default mapping might not be intuitive but anyway the following setting might work for easy cases.

call textobj#functioncall#add('\m\\\a\+\*\?\%({[^}]*}\)*\%(\[[^]]*\]\)*', '{', '}', '', 'tex')

I wrote this a few years ago. This tiny textobject plugin is not always perfect, but works well in many cases. Actually the stripped version of it is now bundled in vim-sandwich for magicchar-f. It may be reusable for future.

machakann avatar Mar 19 '17 13:03 machakann

@lervag @machakann

thanks for your feedback.

  1. Maybe a small improvement could be support for one argument in square brackets. Real-world examples would be:

    \chapter[toctitle with spaces]{title}
    \todo[inline]{hello}
    \framebox[1in]{text}
    

    vimtex-textobject ac already supports this.

  2. Following cases would still not work.

    \textcolor{red}{hello}
    \definecolor{frame}{rgb}{.7,1,.7}
    \fcolorbox{blue}{blue!40}{what do I do here?}
    \multicolumn{2}{r}{\textbf{columns x and x+1}
    \newcommand*{\cmd}[nargs][optargdefault]{defn}
    \subcaptionbox[⟨list entry⟩]{⟨heading⟩}[⟨width⟩][⟨inner-pos⟩]{⟨contents⟩}
    

    If lervags first proposal is possible, this would certainly be the best solution.

  3. vim-textobj-functioncall

    call textobj#functioncall#add('\m\\\a\+\*\?\%({[^}]*}\)*\%(\[[^]]*\]\)*', '{', '}', '', 'tex')
    

    does not work perfectly:

    \todo[inline]{hello}
    

    If the cursor is on the word inline, vaf selects only \todo[inline] and not the complete line. I tried also without the additional curly blocks and it still does not match everything. This feels unexpected since the curly braces are explicitly set as bra and ket. Do you know why vaf does not select in this case everything?

kiryph avatar Mar 19 '17 17:03 kiryph

I've forgotten many things... The reason why vaf behave weird is that the default rules are still valid. It tries to select not only foo(bar) also foo[bar] for convenience. Try below.

call textobj#functioncall#setlist([
      \   {'header': '\m\\\a\+\*\?\%({[^}]*}\)*\%(\[[^]]*\]\)*', 'bra': '{', 'ket': '}'},
      \   {'header': '\m\\\a\+\*\?\%(\[[^]]*\]\)*\%({[^}]*}\)*', 'bra': '{', 'ket': '}'},
      \ ], 'tex')

More precisely, it might be better to add rules for each command separately if possible.

let s:commands = [
      \   {'header': '\m\C\\textcolor{[^}]}', 'bra': '{', 'ket': '}'},
      \   {'header': '\m\C\\definecolor\%({[^}]}\)\{2}', 'bra': '{', 'ket': '}'},
      \ ]
call textobj#functioncall#setlist(s:commands, 'tex')

machakann avatar Mar 20 '17 14:03 machakann