palantir / python-language-server

An implementation of the Language Server Protocol for Python
MIT License
2.61k stars 283 forks source link

Transform source in a plugin #779

Closed delfick closed 4 years ago

delfick commented 4 years ago

Hi,

Is there a hook I can use so that I can transform the source of the file before pyls works on it?

I want to make a plugin so that pyls can understand my custom codec from https://noseofyeti.readthedocs.io/en/latest/api/examples.html

So far I've figured out I can do the following so pyls stops complaining about the coding being unrecognised.

from noseOfYeti.tokeniser.spec_codec import register

from pyls import hookimpl

@hookimpl(hookwrapper=True)
def pyls_initialize(config, workspace):
    register()
    yield

But as far as I can tell there isn't a way to then use the codec to transform the contents of the file.

delfick commented 4 years ago

Never mind, figured it out!

from noseOfYeti.tokeniser.spec_codec import codec

from pyls import hookimpl
import re

spec_codec = codec()

regexes = {
    "encoding": re.compile(r"#\s*coding\s*:\s*spec"),
    "first_whitespace": re.compile(r"^(\s*)"),
}

@hookimpl(hookwrapper=True)
def pyls_initialize(config, workspace):
    spec_codec.register()
    yield

@hookimpl(hookwrapper=True)
def pyls_document_did_open(config, workspace, document):
    contents = document._source
    lines = contents.split("\n")
    if contents and regexes["encoding"].match(lines[0]):
        translated = spec_codec.translate(contents)
        translated = translated.split("\n")[: len(lines)]

        replacement = []
        for orig, new in zip(lines, translated):
            or_space = regexes["first_whitespace"].search(orig).groups()[0]
            tr_space = regexes["first_whitespace"].search(new).groups()[0]

            replacement.append(f"{or_space}{new[len(tr_space):]}")

        document._source = "\n".join(replacement)
    yield