Creating Your Own Language Server Protocol (LSP) for Vim with Python's PyGLS
In the ever-evolving landscape of software development, the Language Server Protocol (LSP) has emerged as a game-changer, enabling developers to enhance their coding experience with powerful features like autocompletion, error checking, and more. If you're a Vim enthusiast looking to supercharge your editor with custom language support, you're in the right place. In this blog post, I'll walk you through the process of creating your own LSP using the Python pygls library.
GNU diction command is a Unix utility designed to analyze a text file against a predefined set of rules, helping writers enhance their writing style by pinpointing potential issues. Rules are stored in an external text file as a two-column table, named with the corresponding language code (e.g., 'en' for English).
As shown in the video below, we will create a simple LSP in Python that integrates with VIM, and which is capable to provide real-time suggestions from diction's rules text files.
First step is setting up a virtual environment using my handy vox PowerShell script, ensuring that our project dependencies are neatly contained. Then, the pygls (pronounced like pie glass) library can be installed with:
pip install pygls
which provides the tools required to build our language server.
The entire language server is less than 70 lines of code python script, shown below:
"""
Copyright (c) 2024 S. Tessarin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import logging
from pygls.server import LanguageServer
from lsprotocol import types
import re
def read_dict(file_path):
result_dict = {}
with open(file_path, 'r') as file:
for line in file:
# Skip comments
if line.startswith('#'):
continue
if line:
parts = line.strip().split('\t')
if len(parts) >= 2:
key = parts[0].strip()
value = parts[1].strip()
result_dict[key] = value
return result_dict
server = LanguageServer("diction-lsp-server", "v0.1")
@server.feature(types.TEXT_DOCUMENT_HOVER)
def hover(ls: LanguageServer, params: types.HoverParams):
pos = params.position
document_uri = params.text_document.uri
document = ls.workspace.get_text_document(document_uri)
try:
line = document.lines[pos.line]
except IndexError:
return None
for word in re.findall(r'(?<!\w)[*]*([\w]+)[*]*(?!\w)',line):
try:
value = f"{word}: {server.dict[word]}"
break
except KeyError:
pass
else:
return None
hover_content = [ "\n",
value,
"\n"]
return types.Hover(
contents=types.MarkupContent(
kind=types.MarkupKind.Markdown,
value="\n".join(hover_content),
),
range=types.Range(
start = types.Position(line=pos.line, character=0),
end = types.Position(line=pos.line + 1, character=0),
),
)
if __name__ == "__main__":
logging.basicConfig(filename='lsp.log',level=logging.INFO, format="%(message)s")
dictionary = read_dict("en")
words_list = list(dictionary.keys())
server.words_list = words_list
server.dict = dictionary
server.start_io()
To ensure that Vim works seamlessly with our newly created LSP, you need to install the popular yegappan/lsp plugin.
The configuration provided below initializes the LSP for MediaWiki, Markdown, and Typst text files. Please note that it is limited to the directory containing the LSP server (lsp.py) and diction's rules file.
\#{
\ name: 'pygls',
\ filetype: ['mediawiki','markdown','typst'],
\ path: 'py',
\ args: ['lsp.py'],
\ },
To change the background color of the pop-up, you can add the following option to your syntax file or .vimrc:
highlight Pmenu guibg=darkblue