How to do something in a function based on the character under the cursor in Vim? - vim

I’m writing a function that edits a certain environment in LaTeX.
The environment basically looks like this:
\begin{quicktikz}
...some stuff...
\end{quicktikz}
or like this:
\begin*{quicktikz}
...some stuff...
\end{quicktikz}
I want to write a function that toggles between the two, when called from within the environment. Since my Vim knowledge ain’t all that, I’m coming up with a simple solution:
Get cursor position with let save_cursor=getpos(".").
Backward search for \begin{quicktikz} using: ?\\begin{quicktikz}\|\\begin\*{quicktikz}.
Search for the { and move left using: normal 0f{h.
Check if the item under cursor equals *:
if it does, do normal x;
if it doesn’t, do normal a*<esc>.
Restore cursor position using call setpos('.',save_cursor).
I know how to do all of this except for step 3. How can I check if the char under the cursor equals to * or not?
If you know a better way of doing this, sharing this would be welcome.

I think the easiest way to retrieve the char under cursor is:
getline(".")[col(".")-1]
Alternatively, you can do it with strpart()
strpart(getline("."), col(".")-1, 1)
The first expression first calls the function getline() passing "." as
argument which means the line where the cursor is positioned will be returned.
Then we use the so called expr8 or expr-[] (see the help) to retrieve a
single character. The number passed comes from another function, col()
which returns the current cursor column. As indexes start in 0, one is subtracted.
You can use it like
if getline(".")[col(".")-1] == '*'
...

Let me propose an alternative implementation of the technique you describe.
:?\\begin\>\zs\*\=?s//\='*'[submatch(0)!='']/|norm!``
The above command consists of two separate commands chained with | (see
:help :bar) in one line. The first one is a substitution (see :help :s)
performed for each line in the specified range,
?\\begin\>\zs\*\=?
According to the range syntax (see :help :range), this range specifies the
only line, that is the previous line where the \\begin\>\zs\*\= pattern
matches the word begin preceded with a backslash and followed by by optional
star character.1 The \zs atom between parts of the pattern
matching \begin and *, sets the start of the match there. So, the match
of the whole pattern is either empty or contains single star character. This
is not necessary for specifying a line in the range, it is useful for reusing
the same pattern later in the :substitute command, where only that star
character or its empty place should be replaced. For details about the
pattern's syntax see :help /\>, :help /\=, :help /\zs.
The substitution itself,
s//\='*'[submatch(0)!='']/
replaces the first occurrence of the last search pattern (which is set by the
backward search in the range) with a string to which the expression
'*'[submatch(0)!=''] evaluates (see :help sub-replace-\=). As the pattern
matches only an empty string or a star character, the subexpression
submatch(0)!='' evaluates to zero if there is no star after \begin, or to
one otherwise. Zero subscript from the string '*' results in a substring
containing the first character of that one-character string. Index one is
equal to the length of the string, therefore subscript results in an empty
string. Thus, when there is a star after \begin, it gets replaced with an
empty string, when a star is not present, zero-width interval just after
\begin is substituted with *.
The second command,
:norm!``
takes advantage of the fact that :substitute command stores the current
cursor position before it actually starts replacement. The `` movement
command jumps back to the position before the latest jump (which occurs in the
aforementioned substitution command) restoring position of the
cursor.2
1 Be careful with search, since in ranges, as usual, it wraps
around the end of file, when the wrapscan option is enabled (it is turned on
by default).
2 Do not confuse `` with the '' command which moves the
cursor to the first non-blank character in the line of the location before the
latest jump.

Related

Select first char up to first non camelCase or non upper case char or up to first snake case _ in vim

I used this map:
map ,w v/\([^ a-z0-9]\|[^ A-Z0-9]\)*<cr>h
the idea is to select
in the words
mysuperTest
MYSUPER_TEST
mysuper_test
to always select the part that says mysuper
but it doesnt work, not sure why
I would use something like the below:
nnoremap ,w v/\C\%#.\([a-z]\+\<bar>[A-Z]\+\)\zs<cr>h
One point to notice is that in a mapping you need to use <bar> (or escape | with an extra backslash) since otherwise | is recognized as a command separator (see :help map-bar.)
Another one to notice is that you want the match to start at the first character outside the word (so you'll land at the end of the word with the h). The visual selection will expand to the start of the match in a search. I suggest using \zs to set the start of the match explicitly (see :help /\zs.)
Finally, beware of 'ignorecase' or 'smartcase' settings. Use \C to explicitly request a case-sensitive match (see :help /\C.)
I also like the idea of using a stronger anchor for the start of the match, so I'm using \%# to match the current cursor position (see :help /\%#), so you're always sure to match the current word only and not end up wandering through the buffer.
Putting it all together:
\C Case-sensitive search
\%# From cursor position
. Skip first character
\( Either one of:
[a-z]\+ One or more lowercase letters
\| (\<bar>) Or:
[A-Z]\+ One or more uppercase letters
\) End group
\zs Set match position here
I'm skipping the first character under the cursor, since in a CamelCase word, the first character won't match capitalization of the remainder of the word.
I kept your original idea of finding the first character after the word then using h to go back one to the left. But that might be a problem if, for example, the word is at the end of the line.
You can actually match the last character of the word instead with something like [a-z]\+\zs[a-z], which will set the start of the match on the last lowercase character. You can do this for both sides of the group (you can have more than one \zs in your pattern, last wins.) If you structure your match that way, you won't need the final h to go back.
I didn't handle numbers, I'll leave those as an exercise to the reader.
Finally, consider there are quite a few corner cases that can make such a mapping quite tricky to get right. Rather than coming up with your own, why not look at plug-ins which add support for handling CamelCase words that have been battle-tested and will cover use cases a lot more advanced than the simple expression you're using here?
There's the excellent vim-scripts/camelcasemotion by Ingo Karkat which sets up a ,w mapping to move to the start of the next CamelCase word, but also i,w to select the current one. You can use powerful combinations such as v3i,w to visually select the current and next two CamelCase words.
You might also check Tim Pope's tpope/vim-abolish which, among other features, defines a set of cr mappings to do coercion from camelCase to MixedCase, snake_case, UPPER_CASE, etc. (Not directly about selecting them, but still related and you might find it useful.)

Emacs: How to delete a line starting with specific text

How can I find and delete lines which start with the text in?
I use the command C-M-s ^in to find all lines starting with in, but then I don't really know what to do.
M-x flush-lines RET ^in RET
C-h f flush-lines tells you:
flush-lines is an interactive compiled Lisp function in replace.el.
It is bound to menu-bar edit flush-lines.
(flush-lines REGEXP &optional RSTART REND INTERACTIVE)
Delete lines containing matches for REGEXP.
When called from Lisp (and usually when called interactively as
well, see below), applies to the part of the buffer after point.
The line point is in is deleted if and only if it contains a
match for regexp starting after point.
If REGEXP contains upper case characters (excluding those preceded by \)
and search-upper-case is non-nil, the matching is case-sensitive.
Second and third arg RSTART and REND specify the region to operate on.
Lines partially contained in this region are deleted if and only if
they contain a match entirely contained in it.
Interactively, in Transient Mark mode when the mark is active, operate
on the contents of the region. Otherwise, operate from point to the
end of (the accessible portion of) the buffer. When calling this function
from Lisp, you can pretend that it was called interactively by passing
a non-nil INTERACTIVE argument.
If a match is split across lines, all the lines it lies in are deleted.
They are deleted before looking for the next match. Hence, a match
starting on the same line at which another match ended is ignored.
query-replace-regexp "in.*" to "" will be work. you should not input " to the prompt

How to replace characters until matching character sequence?

I know that in command mode, ct{char} removes all text from current cursor position till next matching {char}, which can then be replaced by continuing to type.
For example, suppose the text is:
abcdefgh
and cursor is on b, then typing ctg will remove bcdef, which can be replaced by continuing to type.
But this works only for a single matching character. Is it possible to do this by matching a character sequence, for example, using gh instead of g, in above example?
Note: I know that the s/// could be used, but this is a little faster, and more convenient.
Yes. Use the search command:
c/gh
The search is a motion for the previous command. So it will delete characters until if finds a gh string.

Vim: substitution in a range that is less than a line

Let's say I have the following line of code:
something:somethingElse:anotherThing:woahYetAnotherThing
And I want to replace each : with a ; except the first one, such that the line looks like this:
something:somethingElse;anotherThing;woahYetAnotherThing
Is there a way to do this with the :[range]s/[search]/[replace]/[options] command without using the c option to confirm each replace operation?
As far as I can tell, the smallest range that s acts on is a single line. If this is true, then what is the fastest way to do the above task?
I'm fairly new to vim myself; I think you're right about range being lines-only (not 100% certain), but for this specific example you might try replacing all of the instances with a global flag, and then putting back the first one by omitting the global -- something like :s/:/;/g|s/;/:/.
Note: if the line contains a ; before the first : then this will not work.
Here you go...
:%s/\(:.*\):/\1;/|&|&|&|&
This is a simple regex substitute that takes care of one single not-the-first :.
The & command repeats the last substitute.
The | syntax separates multiple commands on one line. So, each substitute is repeated as many times as there are |& things.
Here is how you could use a single keystroke to do what you want (by mapping capital Q):
map Q :s/:/;/g\|:s/;/:<Enter>j
Every time you press Q the current line will be modified and the cursor will move to the next line.
In other words, you could just keep hitting Q multiple times to edit each successive line.
Explanation:
This will operate globally on the current line:
:s/:/;/g
This will switch the first semi-colon back to a colon:
:s/;/:
The answer by #AlliedEnvy combines these into one statement.
My map command assigns #AlliedEnvy's answer to the capital Q character.
Another approach (what I would probably do if I only had to do this once):
f:;r;;.
Then you can repeatedly press ;. until you reach the end of the line.
(Your choice to replace a semi-colon makes this somewhat comfusing)
Explanation:
f: - go to the first colon
; - go to the next colon (repeat in-line search)
r; - replace the current character with a semi-colon
; - repeat the last in-line search (again)
. - repeat the last command (replace current character with a semi-colon)
Long story short:
fx - moves to the next occurrence of x on the current line
; repeats the last inline search
While the other answers work well for this particular case, here's a more general solution:
Create a visual selection starting from the second element to the end of the line. Then, limit the substitution to the visual area by including \%V:
:'<,'>s/\%V:/;/g
Alternatively, you can use the vis.vim plugin
:'<,'>B s/:/;/g

Vim: delete until character for all lines containing a pattern

I'm learning the power of g and want to delete all lines containing an expression, to the end of the sentence (marked by a period). Like so:
There was a little sheep. The sheep was black. There was another sheep.
(Run command to find all sentences like There was and delete to the next period).
The sheep was black.
I've tried:
:g/There was/d\/\. in an attempt to "delete forward until the next period" but I get a trailing characters error.
:g/There was/df. but get a df. is not an editor command error.
Any thoughts?
The action associated with g must be able to act on the line without needing position information from the pattern match that g implies. In the command you are using, the delete forward command needs a starting position that is not being provided.
The problem is that g only indicates a line match, not a specific character position for it's pattern match. I did the following and it did what I think you want:
:g/There was/s/There was[^.]*[.]//
This found lines that matched the pattern There was, and performed a substitution of the regular expression There was[^.]*[.] with the empty string.
This is equivalent to:
:1,$s/There was[^.]*[.]//g
I'm not sure what the g is getting you in your use case, except the automatic application to the entire file line range (same as 1,$ or %). The g in this latter example has to do with applying the substitution to all patterns on the same line, not with the range of lines affected by the substitution command.
I'd just use a regex:
%s/There was\_.\{-}\.\s\?//ge
Note how \_. allows for cross-line sentences
You can use :norm like this:
:g/There was/norm 0weldf.
This finds lines with "There was" then executes the normal commands 0weldf..
0: go to beginning of line
w: go to next word (in this case, "was")
e: go the end of the word (so cursor is on the 's' of "was")
l: move one character to the right (so we don't delete any of "was")
df.: delete until the next '.', inclusive.
If you want to keep the period use dt. instead of df..
If you don't want to delete from the beginning of the line and instead want to do sentences, the :%s command is probably more appropriate here. (e.g. :%s/\(There was\)[^.]*\./\1/g or %s/\(There was\)[^.]*\./\1./g if you want to keep the period at the end of the sentence.
Use search and replace:
:%s/There was[^.]*\.\s*//g

Resources