Code Lens¶
This implements the textDocument/codeLens and codeLens/resolve requests.
In VSCode a code lens is shown as “ghost text” above some line of actual code in your document. These lenses are typically used to show some contextual information (e.g. number of references) or provide easy access to some command (e.g. run this test).
This server scans the document for incomplete sums e.g. 1 + 1 = and returns a code
lens object which, when clicked, will call the codeLens.evaluateSum command to fill
in the answer.
Note that while we could have easily compute the command field of the code lens up
front, this example demonstrates how the codeLens/resolve can be used to defer this
computation until it is actually necessary.
import logging
import re
import attrs
from lsprotocol import types
from pygls.cli import start_server
from pygls.lsp.server import LanguageServer
ADDITION = re.compile(r"^\s*(\d+)\s*\+\s*(\d+)\s*=(?=\s*$)")
server = LanguageServer("code-lens-server", "v1")
@server.feature(types.TEXT_DOCUMENT_CODE_LENS)
def code_lens(params: types.CodeLensParams):
"""Return a list of code lens to insert into the given document.
This method will read the whole document and identify each sum in the document and
tell the language client to insert a code lens at each location.
"""
items = []
document_uri = params.text_document.uri
document = server.workspace.get_text_document(document_uri)
lines = document.lines
for idx, line in enumerate(lines):
match = ADDITION.match(line)
if match is not None:
range_ = types.Range(
start=types.Position(line=idx, character=0),
end=types.Position(line=idx, character=len(line) - 1),
)
left = int(match.group(1))
right = int(match.group(2))
code_lens = types.CodeLens(
range=range_,
data={
"left": left,
"right": right,
"uri": document_uri,
},
)
items.append(code_lens)
return items
@attrs.define
class EvaluateSumArgs:
"""Represents the arguments to pass to the ``codeLens.evaluateSum`` command"""
uri: str
"""The uri of the document to edit"""
left: int
"""The left argument to ``+``"""
right: int
"""The right argument to ``+``"""
line: int
"""The line number to edit"""
@server.feature(types.CODE_LENS_RESOLVE)
def code_lens_resolve(ls: LanguageServer, item: types.CodeLens):
"""Resolve the ``command`` field of the given code lens.
Using the ``data`` that was attached to the code lens item created in the function
above, this prepares an invocation of the ``evaluateSum`` command below.
"""
logging.info("Resolving code lens: %s", item)
left = item.data["left"]
right = item.data["right"]
uri = item.data["uri"]
args = EvaluateSumArgs(
uri=uri,
left=left,
right=right,
line=item.range.start.line,
)
item.command = types.Command(
title=f"Evaluate {left} + {right}",
command="codeLens.evaluateSum",
arguments=[args],
)
return item
@server.command("codeLens.evaluateSum")
def evaluate_sum(ls: LanguageServer, args: EvaluateSumArgs):
logging.info("arguments: %s", args)
document = ls.workspace.get_text_document(args.uri)
line = document.lines[args.line]
# Compute the edit that will update the document with the result.
answer = args.left + args.right
edit = types.TextDocumentEdit(
text_document=types.OptionalVersionedTextDocumentIdentifier(
uri=args.uri,
version=document.version,
),
edits=[
types.TextEdit(
new_text=f"{line.strip()} {answer}\n",
range=types.Range(
start=types.Position(line=args.line, character=0),
end=types.Position(line=args.line + 1, character=0),
),
)
],
)
# Apply the edit.
ls.workspace_apply_edit(
types.ApplyWorkspaceEditParams(
edit=types.WorkspaceEdit(document_changes=[edit]),
),
)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format="%(message)s")
start_server(server)