Table of contents
- Introduction (vim_lib)
- Plugin Manager without fatal flaws (vim_lib, vim_plugmanager)
- Project level and file system (vim_prj, nerdtree)
- Snippets and File Templates (UltiSnips, vim_template)
- Compiling and doing anything (vim-quickrun)
- Work with Git (vim_git)
- Deploy (vim_deploy)
- Testing with xUnit (vim_unittest)
- The library on which everything is kept (vim_lib)
- Other useful plugins
The main problem when writing plugins for Vim is code repetition. Unfortunately, there are no libraries for Vim that solve a lot of basic tasks, which is why all authors of plugins constantly tread on the same rake. In this article I will try to sanctify the solution to this problem.
Foreword
To my (and maybe your) deepest regret, I already wrote this article once, but due to my own stupidity and “features” of the habr, I lost its most interesting chapter. In a fit of rage, I decided not to rewrite it again, as I was so tired, because, dear reader, some of my thoughts will be lost. Fortunately, the lost chapter was an introductory one and only pursued the goal to interest the reader. Nothing important was missed, but still offensive.
Objects
I already wrote in Habré about my attempts to implement classes in Vim. The case ended with a three-fold rewriting of the decision until I came to the solution now available. It is based on the ideas of objects in Perl and uses prototyping.
Consider a few examples:
Please note that to create an instance of a class you need:
- Call the bless method, which will create an object. In the example, the method takes no parameters, which is allowed only when inheriting from the base class. If we want to inherit a class from a child class, we will need to pass an initialized instance of the parent class to this method (let's look at it a little later)
- Initialize the resulting class, setting it to the initial state.
Two questions arise: why
bless is needed and why is it necessary to initialize the received object if
bless is already responsible for it? It's simple. The
bless method makes a very simple operation, it creates a new dictionary and copies everything that is contained in the
properties property, as well as non-static class methods. There are also two links: the
class points to the class itself, and the
parent points to an object of the parent class. At this point, everything becomes more confusing. If you are familiar with how objects are stored in memory in a computer using C ++, then you know that to create an object of a child class, you must create an object of the parent class. For this, the
bless method takes its only parameter. This parameter represents the finished object of the parent class, to which the
parent reference in the child class object will point. A reference to the parent class is used to create this object. Already confused? Everything will fall into place after the following two examples:
Expand method function! s:Class.expand() " {{{ let l:child = {'parent': self} " . {{{ let l:child.expand = self.expand let l:child.mix = self.mix let l:child.new = self.new let l:child.bless = self.bless let l:child.typeof = self.typeof " }}} return l:child endfunction " }}}
Take a look at the implementation of the
expand method. It is called on the parent class to get the descendant class. This method only creates a new dictionary, copies the parent method into it, and creates the
parent property (not to be confused with the object property of the same name), which points to the parent class. This means that all classes in the hierarchy are linked through this property.
Bless method function! s:Class.bless(...) " {{{ let l:obj = {'class': self, 'parent': (exists('a:1'))? a:1 : self.parent.new()} " . {{{ for l:p in keys(self) if type(self[l:p]) == 2 && index(['expand', 'mix', 'bless', 'new', 'typeof'], l:p) == -1 && l:p[0:1] != '__' let l:obj[l:p] = self[l:p] endif endfor " }}} " . {{{ if has_key(self, 'properties') for [l:k, l:v] in items(self.properties) let l:obj[l:k] = deepcopy(l:v) endfor endif " }}} return l:obj endfunction " }}}
After examining the implementation of the
bless method, you will understand how simple class instances are created. The method creates a dictionary with references to the class and object of the parent class, and then copies into it the properties and methods of the class. After an object is created, the designer can set its state in a special way, for example, by calculating the value of some properties or taking them as parameters:
Parameterized constructor function! s:Class.new(x, y) let l:obj = self.bless() let l:obj.x = a:x let l:obj.y = a:y return l:obj endfunction
Has things become easier? But it is still not clear why it is necessary to transfer an object of the parent class to
bless , because the implementation of this method shows that the object is created and installed automatically? It's all about the parameterization of the constructor. If you look again at the first line of the
bless method, you will see that it uses the default no-parameter constructor to create an object of the parent class. What if the parent constructor is parameterized? In this case, we have to personally create an object of the parent class and deliver it to
bless :
Parent class object function! s:Class.new(x, y) let l:parent = self.parent.new(a:x) " let l:obj = self.bless(l:parent) " let l:obj.y = a:y return l:obj endfunction
I hope now is clear. Let's go further:
Since all classes of the library (as well as plug-ins) are stored (as a rule, but not necessarily) in
autoload , they are accessed through scopes with automatic loading of the necessary files. In order not to permanently prescribe these long names, an alias is used by simply assigning a class (after all, a class is an object) to a variable, which is then used in the script. To instantiate a class, the already familiar
new method is used.
')
The base class also provides the following methods:
- typeof - the method accepts the class and checks whether the called class is a child of the parameter
- mix - the method adds all the methods and properties of the class-parameter to the called class, implementing the impurities
In general, the object model in vim_lib ends there, the rest is concrete solutions to various problems.
Library structure
The entire library is located in the
autoload directory. This allows you to load parts of it as needed and use namespaces. The library consists of the following packages:
- base - basic library components, such as dictionaries, arrays, stacks, files, and so on
- sys - system components representing editor logic, such as buffers, plugins, the editor itself
- view widgets
base
The base package contains simple classes that represent low-level components. This includes object representations of basic structures, such as dictionaries, lists, and stacks, as well as supporting classes for working with the file system, event model, and unit tests. If I examine each of these classes, the article will turn into a book, therefore I will limit myself to a cursory review.
sys
This package includes classes that represent the components of the editor and some of its logic. The Buffer, System and Conten classes represent the editor itself, as well as the elements with which it works (buffers and text in them), and Plugin and Autoload define the plug-in model and the editor's initialization as a whole.
view
The view package is still very small, as it contains a single class that represents a simple widget with overlapping stack-based buffers. I selected this package for implementing non-standard solutions in the editor interface.
Editor Initialization Model
The vim_lib # sys # Autoload class deserves special attention, since it defines (but does not impose) the basic logic of initializing the editor and loading plug-ins. This is the only library class that is not inherited from the base Object class, since this was not necessary. By the way, the library does not require using the object model offered to it, it only offers one of the proven implementations that you can use. But let's not go far from the topic. The Autoload class keeps track of which directory will be used in each of the initialization steps to load the components of the editor. These directories are called levels and while three main ones are highlighted:
- System-wide - scripts in this directory are distributed to all users.
- Custom - scripts that are applied to all projects of a particular user.
- Project - scripts for a specific project
To use the proposed model, simply add the following code to
.vimrc :
Autoload connection filetype off set rtp=~/.vim/bundle/vim_lib call vim_lib
The
init method determines the root directory for the current level and the name of the directory that stores plugins. Read more about this in one of my previous
articles .
Plugin model
The vim_lib library also offers a unified plugin model using the vim_lib # sys # Plugin class as the base. This class defines a set of standard methods, as well as implements the plug-in connection logic with checking conditions and dependencies.
A plugin using this model has a structure familiar to all plug-in writers:
- In the plugin directory there is a plugin file containing a child relative to vim_lib # sys # Plugin, which is responsible for initializing the plugin. This class (or rather its object) is responsible for the plug-in options, its initialization, as well as for adding commands and menu items.
- The autoload directory contains the plugin's interface files. These files contain functions that are like plug-in methods. Since the autoload directory is used , all functions are loaded as needed.
- In directories doc , test , syntax and so on, there are other plug-in files (according to the old scheme)
Consider a few examples:
In this simple example, you can see that the plugin is created as an object of class vim_lib # sys # Plugin, which is filled with methods and properties, and then registered in the system. Since the scripts in the
plugin directory are executed when the editor is initialized, this file will be executed every time Vim is started, which allows you to create and register a plug-in object.
The plugin file in the
autoload directory includes the public plugin functions that are used by the commands and menu items of the plugin. This directory may also contain other files used by the plugin, but the
autoload / Plugin name.vim file is the main one. These functions are called when working with the plugin.
To connect the plugin to the editor, simply add the following entry to your
.vimrc :
Plug-in connection filetype off set rtp=~/.vim/bundle/vim_lib call vim_lib
When you declare a plug-in, you can specify its options, hot keys, override commands and menu items.
Unit Tests
The library includes many unit tests thanks to the vim_lib # base # Test class, which implements the basic logic of unit testing using the proposed object model library.
Bye all
The article turned out two times shorter than what I expected. After Habr decided to log me out and reload the page with the article, deleting half the work (I don’t know what it was, maybe a bug), I gathered my strength and finished the article, albeit in an abbreviated version. Behind the scenes, a lot remains, because the article may seem incomprehensible and superficial. To prevent this from happening again, I decided to move away from using the habr as the main platform for the vim_lib project, and use third-party services or my own workouts that preclude such an annoying data loss.
If you find something incomprehensible in this article, ask, I will try to convey the features of the library in a simple and understandable language.