diff --git a/pyls/hookspecs.py b/pyls/hookspecs.py index 5f30cd08..b315689d 100644 --- a/pyls/hookspecs.py +++ b/pyls/hookspecs.py @@ -73,12 +73,12 @@ def pyls_folding_range(config, workspace, document): @hookspec(firstresult=True) -def pyls_format_document(config, workspace, document): +def pyls_format_document(config, workspace, document, options): pass @hookspec(firstresult=True) -def pyls_format_range(config, workspace, document, range): +def pyls_format_range(config, workspace, document, range, options): pass diff --git a/pyls/plugins/autopep8_format.py b/pyls/plugins/autopep8_format.py index f841b64f..3cc47b16 100644 --- a/pyls/plugins/autopep8_format.py +++ b/pyls/plugins/autopep8_format.py @@ -8,7 +8,7 @@ @hookimpl(tryfirst=True) # Prefer autopep8 over YAPF -def pyls_format_document(config, document): +def pyls_format_document(config, document, _options=None): log.info("Formatting document %s with autopep8", document) return _format(config, document) diff --git a/pyls/plugins/yapf_format.py b/pyls/plugins/yapf_format.py index 16afe97d..5287d257 100644 --- a/pyls/plugins/yapf_format.py +++ b/pyls/plugins/yapf_format.py @@ -1,7 +1,7 @@ # Copyright 2017 Palantir Technologies, Inc. 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 pyls import hookimpl @@ -9,12 +9,12 @@ @hookimpl -def pyls_format_document(document): - return _format(document) +def pyls_format_document(document, options=None): + return _format(document, options=options) @hookimpl -def pyls_format_range(document, range): # pylint: disable=redefined-builtin +def pyls_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 @@ -28,17 +28,54 @@ def pyls_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): + # 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( document.source, lines=lines, filename=document.filename, - style_config=file_resources.GetDefaultStyleForDir( - os.path.dirname(document.path) - ) + style_config=style_config ) if not changed: diff --git a/pyls/python_ls.py b/pyls/python_ls.py index 0a11aa9b..5ff5d41f 100644 --- a/pyls/python_ls.py +++ b/pyls/python_ls.py @@ -252,11 +252,11 @@ def document_symbols(self, doc_uri): def execute_command(self, command, arguments): return self._hook('pyls_execute_command', command=command, arguments=arguments) - def format_document(self, doc_uri): - return self._hook('pyls_format_document', doc_uri) + def format_document(self, doc_uri, options): + return self._hook('pyls_format_document', doc_uri, options=options) - def format_range(self, doc_uri, range): - return self._hook('pyls_format_range', doc_uri, range=range) + def format_range(self, doc_uri, range, options): + return self._hook('pyls_format_range', doc_uri, range=range, options=options) def highlight(self, doc_uri, position): return flatten(self._hook('pyls_document_highlight', doc_uri, position=position)) or None @@ -333,9 +333,8 @@ 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) @@ -343,9 +342,8 @@ def m_text_document__rename(self, textDocument=None, position=None, newName=None 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'] diff --git a/test/plugins/test_yapf_format.py b/test/plugins/test_yapf_format.py index e3e198e6..7a312449 100644 --- a/test/plugins/test_yapf_format.py +++ b/test/plugins/test_yapf_format.py @@ -26,6 +26,33 @@ def test_format(workspace): assert len(res) == 1 assert res[0]['newText'] == "A = ['h', 'w', 'a']\n\nB = ['h', 'w']\n" +FOUR_SPACE_DOC = """def hello(): + pass +""" + +def test_format_with_tab_size_option(workspace): + doc = Document(DOC_URI, workspace, FOUR_SPACE_DOC) + res = pyls_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 = pyls_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 = pyls_format_document(doc, { "USE_TABS": True }) + + assert len(res) == 1 + assert res[0]['newText'] == FOUR_SPACE_DOC.replace(" ", "\t") + def test_range_format(workspace): doc = Document(DOC_URI, workspace, DOC)