Highlighting ascii tables - vim

Some (ascii) reports I produce contain ascii tables, like this one:
+------+------+------+
| col1 | col2 | col3 |
+======+======+======+
| bla | bla | bla |
| bla | bla | bla |
| bla | bla | bla |
+------+------+------+
I am trying to find a way to highlight such tables using a vim syntax file. A simple highlighting should suffice - no need to distinguish between the |, the =, the + and the -. However, I do not want to highlight the words inside the table (only the skeleton), and I do not want to highlight -, = signs (etc.) outside of the table.
The problem with vim syntax files is that they have no way of determining what's "up" or "down" relatively to a given point. I would be OK with just highlighting per-line, for examples, lines like this:
+------+------+------+
even if they not create nice tables, but the problem is with lines like this:
| col1 | col2 | col3 |
which may be mixed with non-tabular code, like this Python code:
x = y\
| z | u | v # | is here for 'or'
Can you think of a more elegant way of doing so? I've seen ome highlighters (other than vim) which highlight tables quite well...

You can solve this with containmaint, cp. :help :syn contains. First, define a region that spans the entire range of lines that the table is comprised of. I'm using a simplistic pattern for the header / footer line here, and assert that there's no | immediately above / below (in the neighboring line); refine this as needed:
syn region tableRegion start="|\#<!\n+[-+]*+$" end="^+[-+]*+\n|\#!" contains=tableRow
Then, define the (again, simplistic here) pattern to match the table rows, and mark this contained, so it will only match inside other syntax regions that contains= it.
syn match tableRow "^|.*|$" contained

Related

Word Wrap in Vim (preserving indentation) in Latex document

I was just looking at this post which describes how to wrap entire words in vim. I am looking for a way to key the indentation, or in my case, the lack of it.
I usually select a couple lines with V and press gq to reformat it and re-wrap the lines.
Let say, I have the following document
*Inside of window *Outside of window
|---------------------------------------|
|This is a bla like of text that will wr|ap here
|can you see the wrap |
| |
|---------------------------------------|
The result will be:
*Inside of window *Outside of window
|---------------------------------------|
|This is a bla like of text that will |
|wrap here can you see the wrap |
| |
|---------------------------------------|
So far so good.
Now Let say, I have the following document with some latex tags
*Inside of window *Outside of window
|---------------------------------------|
|\begin{abstract} |
|This is a bla like of text that will wr|ap here
|can you see the wrap |
|\end{abstract} |
| |
|---------------------------------------|
The result is the following, which contains an undesirable \t in the next line
*Inside of window *Outside of window
|---------------------------------------|
|\begin{abstract} |
|This is a bla like of text that will |
| wrap here can you see the wrap |
|\end{abstract} |
| |
|---------------------------------------|
But, my goal is to have the result like the following, without the undesirable \t in the next line.
*Inside of window *Outside of window
|---------------------------------------|
|\begin{abstract} |
|This is a bla like of text that will |
|wrap here can you see the wrap |
|\end{abstract} |
| |
|---------------------------------------|
I tried adding set breakindent to my .vimrc but it didn't work. Currently these are the lines I use regarding wrapping.
set wrap " Wrap
set textwidth=79 " Default Text Width
The indentation for Latex is determined by the GetTeXIndent() function (which can be found out via :setlocal indentexpr?). The corresponding script in $VIMRUNTIME/indent/tex.vim has documentation in its header. It mentions (among others) a config setting that affects the indentation of environments.
" * g:tex_noindent_env
"
" A list of environment names. separated with '\|', where no indentation is
" required. The default is 'document\|verbatim'.
If you add the \begin{...} item there (by putting the configuration into your ~/.vimrc), no additional indent will happen:
let g:tex_noindent_env .= '\|begin'
Caveat: I know very little about Latex syntax; this may have unwanted side effects to formatting elsewhere. Read the entire indent documentation to understand your options for tweaking it.

excel filter data with many headers

I have a really long excel spreadsheet which I need to sort in a really unusual way:
I have many columns, one of which is full of numbers and blank spaces. The column is cut into many parts and is separated by blank spaces. The blank spaces act as the beginning and the end of two areas.
What I need to do is to leave only the numbers that are bigger than 999999999 and smaller than 2000000000 while keeping only the blank spaces adjacent to them. (and filtering all other columns the same way this one column is filtered)
--- Example Table:
Name | ID................. | other data
Bob.. |......................|~-~-~---~-``~
Taxes | 1000077008 | ~~ -`~ `~ ~--
Alice |......................| ~~--~-~ ~_~
Carel |......................|~~ ~ ~--_ ~~
Beans | 2000007804 | ~ ~_~ `~ ~~ `
Coffee| 1000078363 | ~ ~-`--`-` `_~-
--- Example Filtered Table:
Name | ID................. | other data
Bob.. |......................|~-~-~---~-``~
Taxes | 1000077008 | ~~ -`~ `~ ~--
Carel.|......................|~~ ~ ~--_ ~~
Coffee| 1000078363 | ~ ~-`--`-` `_~-
The spaces in front of the numbers show that the format is Text. Change the format in Excel to General or to Numeric and use a custom filter to achieve what you want.
This is an example of a custom filter:
If you cannot change the format, then use the =INT(TRIM(A1)) formula in Excel and sort them.

Specific concatenation of text cells by formula

I'm having difficulty producing a CONCATENATE formula that combines text cells in the way that I want. There are five fields that I want to concatenate: Title, Forename, RegnalNumber, Surname, and Alias, in that order. I'm no regex expert, so excuse the poor formatting, but this is a rough way of expressing what I'm trying to achieve:
(title)? (forename) (regnalnumber)? (surname)?, (alias).
The only field that can't be null is the forename field, although it might have the value "?", in which case it shouldn't output anything in the concatenation, i.e. it should be treated as blank. Hopefully the following test cases should demonstrate the output I'm trying to achieve: the output on the right is what it should look like:
| Title | Forename | RN | Surname | Alias | CONCATENATE |
+--------+----------+----+-----------+--------------+---------------------------------------+
| Ser | Jaime | | Lannister | Kingslayer | Ser Jaime Lannister, Kingslayer |
| | Pate | | | | Pate |
| Lord | ? | | Vance | | Lord Vance |
| King | Aerys | II | Targaryen | The Mad King | King Aerys II Targaryen, The Mad King |
| Lord | Jon | | Arryn | | Lord Jon Arryn |
| | Garth | | | Of Oldtown | Garth, Of Oldtown |
I've experimented for ages trying to make this concatenation work, but haven't been able to get it right. This is the current formula, with cell references replaced by the field name for comprehensibility:
=CONCATENATE(IF(Title<>"",Title&" ",""),IF(AND(Forename<>"",Forename<>"?"),Forename,""),IF(RN<>""," "&RN,""), IF(OR(AND(Forename<>"", Forename<>"?"), Surname<>"", RN<>""), " ",""), IF(Surname<>"",Surname,""),IF(AND(Alias<>"",OR(Alias<>"",AND(Forename<>"", Forename<>"?"),Surname<>"")),", "&Alias, Alias))
There is one case where it doesn't work: if the Surname and RN are null but the the Forename and Alias are non-null. For example, if the Forename is Garth, and the Alias is Of Oldtown, the concatenation outputs: Garth , Of Oldtown. It's the same if the title is non-null. It shouldn't have a space before the comma.
Can you help me to fix this formula so it works as expected? If you can find a way to simplify it, even better! I know I'm probably overcomplicating this a great deal. I'm using LibreOffice Calc 4.3.1.2, not Excel.
The best way imho to solve situations like this is to divide the problem over multiple simple columns, rather than 1 huge complex formula. Remember you can always hide the columns that you don't want to see.
So create a column for Title that says =if(a2="","",a2&" ").
That can be extended for all the other columns, except:
for Forename, where you want to include the "?" as follows: =if(b2="?","",b2&" ")
for Alias, where you want to include the leading ",": =if(e2="","",", "&e2)
Lastly just concatenate each of your working columns with something like: =f2&g2&h2&i2&j2.
This breaks the problem down into very simple components, and makes it easy to debug. If you want to add extra functionality at a later stage, it is easy to swap out one of your formulae for something else.
I know this is only a bit of fun, but can I suggest a more algorithmic approach?
The algorithm is:-
If a field is empty or ?, do nothing
Else
If concatenation so far is empty, add field to concatenation
Else
Add a space followed by the field to concatenation
which leads to this formula in G2 :-
=IF(OR(A2="",A2="?"),F2,IF(F2="",A2,F2&" "&A2)
(need to put single apostrophes in column F to make it work)
which when copied across looks like this:-

Delete a specific Column in VIM/gvim with out using Visual Block mode

Sample Input File
+--------------------+---------+---------
| Name | S1 | S2
+--------------------+---------+---------
| A | -4.703 | -2.378
| B | -3283.2 | -3204.5
| C | 8779 | 7302
| D | 22078 | 18018
+--------------------+---------+---------
It is required to remove the S1 Column, i.e
Desired Output
+--------------------+---------
| Name | S2
+--------------------+---------
| A | -2.378
| B | -3205.5
| C | 7302
| D | 18018
+--------------------+---------
Can anyone help with this
thanks
Look, ma: no visual (block) mode !
My pragmatic approach wins would be: look for column anchors (-+-)
/-+-
Now, the column deletion is as simple as
d<C-v>N
(delete, block-wise, to the next occurrence of the column anchor from the end of the document).
Job done.
Fancy options
To account for multiple columns, you'd like to be precise about which column to match
This needs a little extra oomph
0f+
:exec '/\%' . col('.') . 'v\v[+|]'Enter
NC-vN
t+d
To see more about this \%22v way to select a virtual column, see
Support in vim for specific types of comments
In command mode:
:%s/^\([[+|][^+|]\+\)[+|][^+|]\+/\1/
This uses vim's built-in sed-like search and replace command. Here's the breakdown:
% - for the entire file
s - search for
/^ - the start of line
\([[+|][^+|]\+\) - followed by + or |, followed by any number (\+) of anything that is not + or |. This will get the first column, which we want to keep, so put it in a capture group by surrounding it with \( and \)
[+|][^+|]\+ - followed by + or |, followed by any number (\+) of anything that is not + or |. This will get the second column, which we don't want to keep, so no capture group.
/\1/ - replace everything we matched with the first capture group (which contains the first column). This effectively replaces the first and second column with the contents of the first column.
Like I said, vim's regex are pretty much identical to sed, so you if you look through this tutorial on sed you'll probably pick up a lot of useful stuff for vim as well.
Edit
In response to the OP's request to make this more generally capable of deleting any column:
:%s/^\(\([[+|][^+|]\+\)\{1\}\)[+|][^+|]\+/\1/
The index inside of the \{\}, now deletes the column indicated. Think of it like an array index (i.e. starts at zero). So \{0\} now deletes the first column, \{1\} deletes the second, and so on.
I would like to write Mathias Schwarz's comment into an answer because Visual Mode is the natural way for the task, although there is already an accepted answer.
Assuming cursor is in ¶
+--------------------+¶--------+---------
| Name | S1 | S2
+--------------------+---------+---------
| A | -4.703 | -2.378
| B | -3283.2 | -3204.5
| C | 8779 | 7302
| D | 22078 | 18018
+--------------------+---------+---------
Use normal command Ctrl-V8jf+d to select S1 column and delete it. Explanation:
Ctrl-V: Enter in Blockwise Visual Mode.
8j: Eigth is the number of rows of the table, it sets cursor at same column but last line.
f+: Move cursor until next + character.
d: delete visual selection.
And result is:
+--------------------+---------
| Name | S2
+--------------------+---------
| A | -2.378
| B | -3204.5
| C | 7302
| D | 18018
+--------------------+---------
If this is the only content of the file, the simplest way is to use this:
:%normal 22|d32|
IF there is more text in the file, specifies the line interval:
:X,Ynormal 22|d32|
Where X and Y is the line interval, for example: 10,17normal 22|d32|
If you're not familiar with the normal command and with the | "motion" there goes a quick explanation:
The normal command execute the following commands in the normal mode;
The | "motion" moves the cursor to a specified column, so 22| moves the cursor to the 22nd column;
Basically what :X,Ynormal 22|d32|does is to move the cursor to the 22nd column (22|) and deletes everything (d) until the 32nd column (32|) for every line specified by X and Y.
Based on patterns of your table, this can be achieved in two simple commands:
:%norm 2f+dF+
:%norm 2f|dF|
Where 2 is your column to remove (1 will remove 1st, 3 - 3rd).
This works as below (for each line at once):
Find second corresponding character of the column (2f+ or 2f|).
Delete backwards to the next found character of the column (dF+ or dF|).
Here is command line approach removing 2nd column in-place:
$ ex +'%norm 2f+dF+' +'%norm 2f|dF|' -scx cols2

Add lines to visual selection via Ex-Mode

Is it possible to add certain lines to a visual selection via an EX-mode command?
I have text in the following form:
+----------+-----------+
| Some text| other text|
+----------+-----------+
| More text||
| And even more ||
| - ...||
+----------+-----------+
And I want the text to be displayed like this:
+---------------+------------+
| Some text | other text |
+---------------+------------+
| More text | |
| And even more | |
| - ... | |
+---------------+------------+
Using the tabular plugin when I delete the lines with + via the following workflow works:
g!/+/d
// Visually select the remaining lines
Tab /|
// Manually insert the +----+----+ lines
I was wondering if there is a way to keep the delimiting lines and visual-select the lines not containing a + via EX-Mode like :g!/+/ add-line-to-visual-selection.
You could use:
:Tab /|\|+/l1
:<range>g/+/s/ /-/g
You can find help for \| in :help /\| or more globally :help pattern, it is the standard way to express alternation in Vim Regular Expressions. So /|\|+/ is a pattern with delimiters that matches either | or +. (Reading the whole :help pattern has excellent return on investment, FYI).
Concerning the /l1 in the Tabular plugin, you can read the help of the plugin more in depth, it will add space after separator and left-align text.

Resources