Skip to content

Add support for LSP formatting options parameter #134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 5, 2022
4 changes: 2 additions & 2 deletions pylsp/hookspecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ def pylsp_folding_range(config, workspace, document):


@hookspec(firstresult=True)
def pylsp_format_document(config, workspace, document):
def pylsp_format_document(config, workspace, document, options):
pass


@hookspec(firstresult=True)
def pylsp_format_range(config, workspace, document, range):
def pylsp_format_range(config, workspace, document, range, options):
pass


Expand Down
4 changes: 2 additions & 2 deletions pylsp/plugins/autopep8_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@


@hookimpl(tryfirst=True) # Prefer autopep8 over YAPF
def pylsp_format_document(config, document):
def pylsp_format_document(config, document, options=None): # pylint: disable=unused-argument
log.info("Formatting document %s with autopep8", document)
return _format(config, document)


@hookimpl(tryfirst=True) # Prefer autopep8 over YAPF
def pylsp_format_range(config, document, range): # pylint: disable=redefined-builtin
def pylsp_format_range(config, document, range, options=None): # pylint: disable=redefined-builtin,unused-argument
log.info("Formatting document %s in range %s with autopep8", document, range)

# First we 'round' the range up/down to full lines only
Expand Down
55 changes: 46 additions & 9 deletions pylsp/plugins/yapf_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging
import os

from yapf.yapflib import file_resources
from yapf.yapflib import file_resources, style
from yapf.yapflib.yapf_api import FormatCode

from pylsp import hookimpl
Expand All @@ -14,12 +14,12 @@


@hookimpl
def pylsp_format_document(document):
return _format(document)
def pylsp_format_document(document, options=None):
return _format(document, options=options)


@hookimpl
def pylsp_format_range(document, range): # pylint: disable=redefined-builtin
def pylsp_format_range(document, range, options=None): # pylint: disable=redefined-builtin
# First we 'round' the range up/down to full lines only
range['start']['character'] = 0
range['end']['line'] += 1
Expand All @@ -33,10 +33,10 @@ def pylsp_format_range(document, range): # pylint: disable=redefined-builtin

# Add 1 for 1-indexing vs LSP's 0-indexing
lines = [(range['start']['line'] + 1, range['end']['line'] + 1)]
return _format(document, lines=lines)
return _format(document, lines=lines, options=options)


def _format(document, lines=None):
def _format(document, lines=None, options=None):
# Yapf doesn't work with CRLF/CR line endings, so we replace them by '\n'
# and restore them below.
replace_eols = False
Expand All @@ -46,13 +46,50 @@ def _format(document, lines=None):
replace_eols = True
source = source.replace(eol_chars, '\n')

# Get the default styles as a string
# for a preset configuration, i.e. "pep8"
style_config = file_resources.GetDefaultStyleForDir(
os.path.dirname(document.path)
)
if options is not None:
# We have options passed from LSP format request
# let's pass them to the formatter.
# First we want to get a dictionary of the preset style
# to pass instead of a string so that we can modify it
style_config = style.CreateStyleFromConfig(style_config)

use_tabs = style_config['USE_TABS']
indent_width = style_config['INDENT_WIDTH']

if options.get('tabSize') is not None:
indent_width = max(int(options.get('tabSize')), 1)

if options.get('insertSpaces') is not None:
# TODO is it guaranteed to be a boolean, or can it be a string?
use_tabs = not options.get('insertSpaces')

if use_tabs:
# Indent width doesn't make sense when using tabs
# the specifications state: "Size of a tab in spaces"
indent_width = 1

style_config['USE_TABS'] = use_tabs
style_config['INDENT_WIDTH'] = indent_width
style_config['CONTINUATION_INDENT_WIDTH'] = indent_width

for style_option, value in options.items():
# Apply arbitrary options passed as formatter options
if style_option not in style_config:
# ignore if it's not a known yapf config
continue

style_config[style_option] = value

new_source, changed = FormatCode(
source,
lines=lines,
filename=document.filename,
style_config=file_resources.GetDefaultStyleForDir(
os.path.dirname(document.path)
)
style_config=style_config
)

if not changed:
Expand Down
18 changes: 8 additions & 10 deletions pylsp/python_lsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,11 @@ def document_symbols(self, doc_uri):
def execute_command(self, command, arguments):
return self._hook('pylsp_execute_command', command=command, arguments=arguments)

def format_document(self, doc_uri):
return self._hook('pylsp_format_document', doc_uri)
def format_document(self, doc_uri, options):
return self._hook('pylsp_format_document', doc_uri, options=options)

def format_range(self, doc_uri, range):
return self._hook('pylsp_format_range', doc_uri, range=range)
def format_range(self, doc_uri, range, options):
return self._hook('pylsp_format_range', doc_uri, range=range, options=options)

def highlight(self, doc_uri, position):
return flatten(self._hook('pylsp_document_highlight', doc_uri, position=position)) or None
Expand Down Expand Up @@ -362,19 +362,17 @@ def m_text_document__hover(self, textDocument=None, position=None, **_kwargs):
def m_text_document__document_symbol(self, textDocument=None, **_kwargs):
return self.document_symbols(textDocument['uri'])

def m_text_document__formatting(self, textDocument=None, _options=None, **_kwargs):
# For now we're ignoring formatting options.
return self.format_document(textDocument['uri'])
def m_text_document__formatting(self, textDocument=None, options=None, **_kwargs):
return self.format_document(textDocument['uri'], options)

def m_text_document__rename(self, textDocument=None, position=None, newName=None, **_kwargs):
return self.rename(textDocument['uri'], position, newName)

def m_text_document__folding_range(self, textDocument=None, **_kwargs):
return self.folding(textDocument['uri'])

def m_text_document__range_formatting(self, textDocument=None, range=None, _options=None, **_kwargs):
# Again, we'll ignore formatting options for now.
return self.format_range(textDocument['uri'], range)
def m_text_document__range_formatting(self, textDocument=None, range=None, options=None, **_kwargs):
return self.format_range(textDocument['uri'], range, options)

def m_text_document__references(self, textDocument=None, position=None, context=None, **_kwargs):
exclude_declaration = not context['includeDeclaration']
Expand Down
27 changes: 27 additions & 0 deletions test/plugins/test_yapf_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"""

GOOD_DOC = """A = ['hello', 'world']\n"""
FOUR_SPACE_DOC = """def hello():
pass
"""


def test_format(workspace):
Expand Down Expand Up @@ -68,3 +71,27 @@ def test_line_endings(workspace, newline):
res = pylsp_format_document(doc)

assert res[0]['newText'] == f'import os{newline}import sys{2 * newline}dict(a=1){newline}'


def test_format_with_tab_size_option(workspace):
doc = Document(DOC_URI, workspace, FOUR_SPACE_DOC)
res = pylsp_format_document(doc, {"tabSize": "8"})

assert len(res) == 1
assert res[0]['newText'] == FOUR_SPACE_DOC.replace(" ", " ")


def test_format_with_insert_spaces_option(workspace):
doc = Document(DOC_URI, workspace, FOUR_SPACE_DOC)
res = pylsp_format_document(doc, {"insertSpaces": False})

assert len(res) == 1
assert res[0]['newText'] == FOUR_SPACE_DOC.replace(" ", "\t")


def test_format_with_yapf_specific_option(workspace):
doc = Document(DOC_URI, workspace, FOUR_SPACE_DOC)
res = pylsp_format_document(doc, {"USE_TABS": True})

assert len(res) == 1
assert res[0]['newText'] == FOUR_SPACE_DOC.replace(" ", "\t")