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
@@ -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


4 changes: 2 additions & 2 deletions pylsp/plugins/autopep8_format.py
Original file line number Diff line number Diff line change
@@ -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
55 changes: 46 additions & 9 deletions pylsp/plugins/yapf_format.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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
@@ -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
@@ -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:
18 changes: 8 additions & 10 deletions pylsp/python_lsp.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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']
27 changes: 27 additions & 0 deletions test/plugins/test_yapf_format.py
Original file line number Diff line number Diff line change
@@ -21,6 +21,9 @@
"""

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


def test_format(workspace):
@@ -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")