• Stars
    star
    412
  • Rank 105,024 (Top 3 %)
  • Language
    Vim Script
  • License
    MIT License
  • Created over 5 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Change an HTML(ish) opening tag and take the closing one along as well

GitHub version Build Status

Tagalong

Basic Usage

The plugin is designed to automatically rename closing HTML/XML tags when editing opening ones (or the other way around). For the most part, you should be able to edit your code normally (see below for limitations) and the plugin would take care of the renaming once you leave insert mode:

Demo

Apart from HTML tags, it'll also detect XML-style <namespaced:tags>, react-style <Component.Subcomponents>, ember-style <component/paths>.

The plugin only activates for HTML-like buffers, or at least all the ones that I could think of. You can see the full list in the setting g:tagalong_filetypes. You can set that variable yourself to limit it to the ones you use. You can activate the plugin for more filetypes by changing g:tagalong_additional_filetypes (but consider opening an issue to suggest changes to the default list). See the "Settings" section for more details.

Features and Limitations

Not every method of changing the tag can be intercepted, or it might be too complicated or too invasive to do so. Here's the methods that work with the plugin:

  • c: Anything involving a c operation, including cw, ci<, cE, or C.
  • v + c: Selecting anything in visual mode and changing it with a c.
  • i, a: Entering insert mode and making direct changes.

For all of these, the cursor needs to be within the <> angle brackets of the tag. If you change it from the outside, like with a C starting at the opening angle bracket, the plugin won't be activated.

A few examples of making a change that WON'T trigger the plugin:

  • Using the :substitute command, for instance :%s/<div /<span /g.
  • Yanking some text and pasting it over.
  • Using the r or x mappings to change/delete one character.
  • Entering insert mode outside of the tag, navigating to it and changing it.

Some of these might be implemented at a later time, but others might be too difficult or too invasive. If you often use a method that doesn't trigger the plugin, consider opening a github issue to discuss it.

Also note that the plugin relies on the InsertLeave autocommand to detect when to apply the change. If you exit insert mode via <c-c>, that won't be triggered. This is a good way to avoid the automatic behaviour, but if you commonly exit insert mode this way, it can be a problem. See the "Internals and Advanced Usage" section for help.

You can disable the plugin for particular mappings by overriding the g:tagalong_mappings variable. See the "Settings" section for details.You can also disable it explicitly by calling the command :TagalongDeinit, and later re-enable it with :TagalongInit. This could be quite useful if you run into a bug and the buffer gets messed up!

Large files can also be a problem. With a 100,000-line XML file and tags that wrap its entirety, the plugin really takes time, even during normal editing within a tag. To avoid this, its operation has been limited to 500 milliseconds, configurable via the g:tagalong_timeout variable. This means, unfortunately, that in said large file, closing tags may not be reliably updated. This should be rare, hopefully.

With g:tagalong_verbose turned on, you'll get a message that the matching tag was NOT updated if the search for it times out. Alternatively, you can set g:tagalong_timeout to 0 for no timeout and, if you see a dramatic slowdown, use <c-c> and run :TagalongDeinit to disable the plugin in this buffer. You could implement an automated solution by checking the value of line('$').

If you have vim-repeat installed, you can repeat the last tag change with the . operator.

Internals and Advanced Usage

The plugin installs its mappings with the function tagalong#Init(). All mappings and variables initialized are buffer-local. Instead of using g:tagalong_filetypes, you can actually just put tagalong#Init() in ~/.vim/ftplugin/<your-filetype>.vim, and it should work. Or you can come up with some other criteria to activate it.

All the mappings (currently) do the following:

  • Call the tagalong#Trigger(<mapping>, <count>) function. It stores information about the tag under the cursor in a buffer-local variable and executes the original mapping.
  • Upon exiting insert mode (see :help InsertLeave), the function tagalong#Apply() gets called, takes the stored tag information and gets the changed tag and applies the change to both opening and closing tag
  • The tagalong#Reapply() function can be invoked by vim-repeat, or it can be invoked manually, to perform the previous tag change.

The initial function, tagalong#Trigger, can also be invoked with no arguments, which means it will only store tag information to prepare tagalong#Apply(). This allows you to do something more complicated between the two calls. For example, if you wanted to make pasting over a tag activate the plugin, it might work like this:

" The `<c-u>` removes the current visual mode, so a function can be called
xnoremap <buffer> p :<c-u>call <SID>Paste()<cr>

" The <SID> above is the same as the s: here
function! s:Paste()
  call tagalong#Trigger()

  " gv reselects the previously-selected area, and then we just paste
  normal! gvp

  call tagalong#Apply()
endfunction

This is not a built-in, because it feels a bit invasive, and there's other plugins (and snippets) that override p. Plus, repeating the operation doesn't seem to quite work. But I hope it's a good example to illustrate how you could try to build something more complicated with the core functions of the plugin.

If you commonly exit insert mode via <c-c>, the plugin won't be triggered, but you can take care of that with a mapping, if you'd like:

inoremap <silent> <c-c> <c-c>:call tagalong#Apply()<cr>

It's generally not recommended -- <c-c> doesn't trigger InsertLeave semi-intentionally, I think, as an "escape hatch". But it depends on how you use it.

Settings

g:tagalong_filetypes

Example usage:

let g:tagalong_filetypes = ['html']

Default value:

['eco', 'eelixir', 'ejs', 'eruby', 'html', 'htmldjango', 'javascriptreact', 'jsx', 'php', 'typescriptreact', 'xml']

This variable holds all of the filetypes that the plugin will install mappings for. If, for some reason, you'd like to avoid its behaviour for particular markup languages, you can set the variable to a list of the ones you'd like to keep.

To add more filetypes, check out g:tagalong_additional_filetypes below.

If you set it to an empty list, [], the plugin will not be automatically installed for any filetypes, but you can activate it yourself by calling the tagalong#Init() function in a particular buffer.

g:tagalong_additional_filetypes

Example usage:

let g:tagalong_additional_filetypes = ['custom', 'another']

Default value: []

The plugin should work with any HTML-like tags, so if a compatible filetype is missing in g:tagalong_filetypes, you can add it here. Or you can call the tagalong#Init() function in your buffer.

Ideally, the above setting would just hold all sensible filetypes, so consider opening a github issue to suggest one that you feel is missing. As long as it's not something too custom, I would likely be okay with adding it to the built-in list.

g:tagalong_excluded_filetype_combinations

Example usage:

let g:tagalong_excluded_filetype_combinations = ['custom.html']

Default value: ['eruby.yaml']

This setting allows the exclusion of particular filetype dot-combinations from initialization. The "filetypes" setting includes single filetypes, so any combination of them including, for instance, "html" would activate the plugin. So you could set custom filetypes like "html.rails" or something and it would still work.

That said, there are combinations that are not HTML-like. The current default value of the setting, "eruby.yaml" is a good example -- it's being set by vim-rails, but there's no HTML to edit in there.

It's recommended to leave this setting untouched, but you could use it as an escape hatch. If you have any problems with a particular filetype that are solved by an entry in this setting, consider opening an issue to make a change to the defaults.

g:tagalong_mappings

Example usage:

let g:tagalong_mappings = [{'c': '_c'}, 'i', 'a']

Default value: ['c', 'C', 'v', 'i', 'a', 'A']

This setting controls which types of editing will have mappings installed for them. Currently, these are literal mappings -- each character in the list is a mapping that you can see by executing :nmap c, for instance. But it's not necessary to be the case -- in the future, the values in the list might be labels of some sort that will be explained in more detail in the documentation.

Changing this variable means that editing the buffer with the removed mappings won't trigger the plugin. You could set it to ['i', 'a'] if you usually edit tags by entering insert mode and backspacing over the tag. That way, the c family of mappings could be remapped by some other plugin, for instance. Or you could use them to give yourself an escape hatch, if the plugin bugs out or you have good reason not to update the other tag.

Note that the plugin will attempt to respect your previous mappings of any of these keys. If you have an nnoremap c in your .vimrc file, it'll be applied. Mapping cw, on the other hand, will likely just use your mapping, instead of hitting the plugin at all. If you're having problems with this, please open a github issue.

If you want to use a special key sequence to replace a built-in, you can put a dictionary instead of a single letter in the setting. For instance, instead of 'c', you can put in {'c': '_c'}, and it would use the _c key sequence to act as the native c key with tagalong's effect. You can use that to avoid conflicts with other plugins overriding the native keys. It's generally not recommended -- the power of the plugin is that it works automatically, otherwise you could use vim-surround instead. But it's your call.

If you set it to an empty list, [], the plugin will not be activated by any mappings, but you can read the "Internals and Advanced Usage" section for other ways of using it.

g:tagalong_verbose

Example usage:

let g:tagalong_verbose = 1

Default value: 0

If you set this value to 1, the plugin will print out a message every time it auto-updates a closing/opening tag. Could be useful if you'd like to be sure the change was made, especially if it's offscreen.

Alternatives

vim-surround gives you an interface to rename tags. It's explicit, rather than automatic, which I find inconvenient for this particular use case. It's older, though, so it likely works more reliably.

Special Thanks

Thanks to @BeatRichardz for coming up with the plugin's name.

Contributing

Pull requests are welcome, but take a look at CONTRIBUTING.md first for some guidelines. Be sure to abide by the CODE_OF_CONDUCT.md as well.

More Repositories

1

splitjoin.vim

Switch between single-line and multiline forms of code
Vim Script
1,918
star
2

switch.vim

A simple Vim plugin to switch segments of text with predefined replacements
Ruby
648
star
3

sideways.vim

A Vim plugin to move function arguments (and other delimited-by-something items) left and right.
Ruby
480
star
4

linediff.vim

A vim plugin to perform diffs on blocks of code
Vim Script
463
star
5

vimrunner

Control a vim instance through ruby code
Ruby
237
star
6

inline_edit.vim

Edit code that's embedded within other code
Vim Script
150
star
7

typewriter.vim

Make cool typewriter sounds in insert mode
Vim Script
88
star
8

bufferize.vim

Execute a :command and show the output in a temporary buffer
Vim Script
83
star
9

Vimfiles

My .vim folder
Vim Script
79
star
10

deleft.vim

Delete a wrapping if-clause, try-catch block, etc. and shift left.
Vim Script
71
star
11

quickpeek.vim

Show a preview popup on quickfix entries
Vim Script
70
star
12

undoquit.vim

Undo a :quit -- reopen the last window you closed
Ruby
64
star
13

diffurcate.vim

Split a git diff into separate files
Ruby
59
star
14

dsf.vim

Delete surrounding function call
Ruby
52
star
15

id3.vim

"Edit" mp3 files with Vim, or rather, their ID3 tags
Vim Script
51
star
16

ember_tools.vim

Tools for working with ember projects
Vim Script
51
star
17

writable_search.vim

Grep for something, then write the original files directly through the search results.
Vim Script
51
star
18

quickmd

Quickly preview a markdown file
Rust
37
star
19

gnugo.vim

Play a game of Go in your text editor, using GnuGo
Vim Script
33
star
20

discotheque.vim

Emphasize pieces of text, with style.
Vim Script
32
star
21

simple_bookmarks.vim

A small plugin to create named bookmarks in Vim
Vim Script
31
star
22

gapply.vim

Before committing, edit a git diff and apply it directly to the index
Vim Script
28
star
23

whitespaste.vim

Automatically adjust number of blank lines when pasting
Vim Script
26
star
24

image-processing

Some experiments with simple image processing algorithms
Ruby
22
star
25

rails_extra.vim

Some extra tools for working with Rails projects, on top of vim-rails
Vim Script
21
star
26

id3-image

A tool to embed images into mp3 files
Rust
18
star
27

vim-eco

Eco (embedded coffee-script) support for Vim
Vim Script
17
star
28

strftime.vim

Make it easier to read and write strftime strings
Vim Script
16
star
29

exercism.vim

Vim plugin to help out with exercism.io. A thin wrapper around the `exercism` command-line
Vim Script
15
star
30

dealwithit.vim

Show the "deal with it" dog animation in Vim
Vim Script
14
star
31

tagfinder.vim

A simple vim plugin to look for tags of specific kinds: classes, functions, etc.
Vim Script
13
star
32

whatif.vim

What if we could see which if-else branch gets executed? Haha jk... unless?
Ruby
12
star
33

ghundle

A package manager for git hooks
Ruby
11
star
34

yankwin.vim

Yank and paste windows around
Ruby
9
star
35

waiting-on-rails

Bored of waiting on "rails server"? No more!
Ruby
9
star
36

cucumber-vimscript

Cucumber step definitions for testing vimscript
Ruby
9
star
37

scripts

Small scripts to solve minor problems
Ruby
9
star
38

qftools.vim

Tools to manipulate the quickfix window
Vim Script
8
star
39

vim-lectures

Lectures for the Vim course in FMI
Vim Script
8
star
40

id3-json

Read and write ID3 tags with machine-readable input and output
Rust
7
star
41

progressor

Measure iterations in a long-running task
Ruby
7
star
42

popup_scrollbar.vim

A scrollbar for Vim windows built with the popup API
Vim Script
7
star
43

coffee_tools.vim

A work-in-progress plugin with tools for working with coffeescript
Vim Script
7
star
44

dotfiles

My linux configuration files
Shell
6
star
45

awesome-config

My awesome configuration ("awesome" as in the window manager, not as a quality)
Lua
5
star
46

better_netrw.vim

A better file manager for Vim
Vim Script
5
star
47

vim-fmi

The site for the Vim course at Sofia University: https://vim-fmi.bg/
Ruby
5
star
48

dot-shell

My zsh configuration
Shell
5
star
49

xmonad-config

My personal xmonad configuration (not maintained, switched to awesome)
Haskell
5
star
50

andrews_nerdtree.vim

My personal collection of NERDTree extensions
Vim Script
4
star
51

ginitpull.vim

Open a pull request directly from Vim
Vim Script
4
star
52

iseven.vim

Check if a number is even or not
Vim Script
4
star
53

rtranslate.vim

Easier translations with rails
Vim Script
3
star
54

archivitor.vim

Vim plugin for editing the contents of archives
Vim Script
3
star
55

libmarks

A small website to bookmark programming libraries. Inspired by The Ruby Toolbox
Ruby
3
star
56

daily-sites

A website to administer links to regularly visited sites
Ruby
3
star
57

simple_gl

A thin wrapper around ruby's opengl bindings
Ruby
3
star
58

rimplement.vim

Implement a ruby class or method.
Vim Script
2
star
59

rust-shooter

A toy game in Rust
Rust
2
star
60

egui-mp3s

A simple egui demo app
Rust
2
star
61

vim-learning-website

A website that uses the vimgolf gem for vim exercises.
Ruby
2
star
62

modsearch.vim

A command that lets you change the previous search in predefined ways
Ruby
2
star
63

rust-hangman

A simple game of hangman, built for learning purposes
Rust
2
star
64

do-after

A small C program to execute something after a set amount of time.
C
2
star
65

ctags_reader

Read ctags files, provide a ruby interface to get data out of them
Ruby
2
star
66

rust-spotiferris

A Rust web experiment -- a music management app (very incomplete)
Rust
2
star
67

subtitles.vim

(Not working) A vim plugin that helps you with editing subtitles
Vim Script
2
star
68

randfiles

A tool that outputs a list of random files in given directories
Ruby
2
star
69

rustbucket.vim

[WIP] A collection of Rust tools for Vim
Vim Script
2
star
70

gtk4-example

A very basic Rust example of using GTK4
Rust
2
star
71

rust_wrap.vim

Ok-wrap (Some-wrap, Rc-wrap, etc.) Rust functions
Vim Script
2
star
72

bioinformatics-experiments

Sandbox to play around with bioinformatics-related stuff
Python
2
star
73

onchange.vim

Trigger an autocommand upon a code change
Vim Script
2
star
74

digits

Simple experimental project that recognizes a digit in a given BMP image
C++
1
star
75

vsnips.vim

An early stage experiment with snippets
Vim Script
1
star
76

hello-rusty-web

Some example code for a web application in Rust
Rust
1
star
77

rust-bookworm

A toy project that indexes txt-formatted books and allows searching through them
Rust
1
star
78

mount_archive

Mount an archive as a virtual filesystem
Ruby
1
star
79

declarators

Useful method decorators for ruby
Ruby
1
star
80

vim-fmi-cli

The command-line tool for https://vim-fmi.bg
Rust
1
star
81

rust-eliza

A simple chatbot in Rust. Not intended for "serious" use (whatever the serious use for a chatbot might be), just an experiment.
Rust
1
star
82

tdd_workshop

Python
1
star
83

sticky_line.vim

[WIP] Pin lines to the window to always keep them visible while scrolling
Vim Script
1
star
84

protein-runway

Integrated Bioinformatics Project
Python
1
star