From ef49aa8512f4844d6be0995e255d31c59cf04cff Mon Sep 17 00:00:00 2001 From: masfrost Date: Tue, 7 Dec 2021 21:36:51 -0800 Subject: [PATCH 1/6] Add support for LSP formatting options to yapf --- pyls/hookspecs.py | 4 ++-- pyls/plugins/autopep8_format.py | 2 +- pyls/plugins/yapf_format.py | 38 ++++++++++++++++++++++++++------ pyls/python_ls.py | 18 +++++++-------- test/plugins/test_yapf_format.py | 19 ++++++++++++++++ 5 files changed, 61 insertions(+), 20 deletions(-) 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..bcdb305e 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 @@ -13,8 +13,9 @@ def pyls_format_document(document): return _format(document) + @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 +29,40 @@ 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, options=None): + # Get the default styles + style_config = file_resources.GetDefaultStyleForDir( + os.path.dirname(document.path) + ) + if options is not None: + # If we have options, let's set them + # First we want to get a dictionary of the styles + style_config = style.CreateStyleFromConfig(style_config) + if options.get('insertSpaces') is not None: + style_config['USE_TABS'] = not (options.get('insertSpaces') in [True, 'true', 'True']) + + if style_config['USE_TABS']: + # indent width doesn't make sense when using tabs + # the specifications state: "Size of a tab in spaces" + style_config['INDENT_WIDTH'] = 1 + style_config['CONTINUATION_INDENT_WIDTH'] = style_config['INDENT_WIDTH'] + + print(style_config['USE_TABS']) + + if options.get('tabSize') is not None and not style_config['USE_TABS']: + style_config['INDENT_WIDTH'] = max(int(options.get('tabSize')), 1) + style_config['CONTINUATION_INDENT_WIDTH'] = style_config['INDENT_WIDTH'] + -def _format(document, lines=None): 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..ff479984 100644 --- a/test/plugins/test_yapf_format.py +++ b/test/plugins/test_yapf_format.py @@ -26,6 +26,25 @@ 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_range_format(workspace): doc = Document(DOC_URI, workspace, DOC) From fe2f00b1b8248461f0549f890e8ff9f1471656fb Mon Sep 17 00:00:00 2001 From: masfrost Date: Tue, 7 Dec 2021 23:34:28 -0800 Subject: [PATCH 2/6] cleanup --- pyls/plugins/yapf_format.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/pyls/plugins/yapf_format.py b/pyls/plugins/yapf_format.py index bcdb305e..6a31dc90 100644 --- a/pyls/plugins/yapf_format.py +++ b/pyls/plugins/yapf_format.py @@ -33,30 +33,36 @@ def pyls_format_range(document, range, options=None): # pylint: disable=redefin def _format(document, lines=None, options=None): - # Get the default styles + # Get the default styles as a string + # for a preset configuration, i.e. "pep8" style_config = file_resources.GetDefaultStyleForDir( - os.path.dirname(document.path) - ) + os.path.dirname(document.path) + ) if options is not None: - # If we have options, let's set them - # First we want to get a dictionary of the styles + # 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: - style_config['USE_TABS'] = not (options.get('insertSpaces') in [True, 'true', 'True']) + # TODO is it guaranteed to be a boolean, or can it be a string + use_tabs = not options.get('insertSpaces') - if style_config['USE_TABS']: + if use_tabs: # indent width doesn't make sense when using tabs # the specifications state: "Size of a tab in spaces" - style_config['INDENT_WIDTH'] = 1 - style_config['CONTINUATION_INDENT_WIDTH'] = style_config['INDENT_WIDTH'] - - print(style_config['USE_TABS']) + indent_width = 1 - if options.get('tabSize') is not None and not style_config['USE_TABS']: - style_config['INDENT_WIDTH'] = max(int(options.get('tabSize')), 1) - style_config['CONTINUATION_INDENT_WIDTH'] = style_config['INDENT_WIDTH'] - + style_config['USE_TABS'] = use_tabs + style_config['INDENT_WIDTH'] = indent_width + style_config['CONTINUATION_INDENT_WIDTH'] = indent_width new_source, changed = FormatCode( document.source, From d94c3b9a7c73bdf2cc3a20225d5aec21c5edd4ef Mon Sep 17 00:00:00 2001 From: masfrost Date: Tue, 7 Dec 2021 23:36:36 -0800 Subject: [PATCH 3/6] oops --- pyls/plugins/yapf_format.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyls/plugins/yapf_format.py b/pyls/plugins/yapf_format.py index 6a31dc90..fe05944e 100644 --- a/pyls/plugins/yapf_format.py +++ b/pyls/plugins/yapf_format.py @@ -9,8 +9,8 @@ @hookimpl -def pyls_format_document(document): - return _format(document) +def pyls_format_document(document, options=None): + return _format(document, options=options) From c25226c4012ef89c5521d439b0132ff3d07506b9 Mon Sep 17 00:00:00 2001 From: masfrost Date: Tue, 7 Dec 2021 23:48:50 -0800 Subject: [PATCH 4/6] support arbitrary configs --- pyls/plugins/yapf_format.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyls/plugins/yapf_format.py b/pyls/plugins/yapf_format.py index fe05944e..fbf83cce 100644 --- a/pyls/plugins/yapf_format.py +++ b/pyls/plugins/yapf_format.py @@ -56,7 +56,7 @@ def _format(document, lines=None, options=None): use_tabs = not options.get('insertSpaces') if use_tabs: - # indent width doesn't make sense when using tabs + # Indent width doesn't make sense when using tabs # the specifications state: "Size of a tab in spaces" indent_width = 1 @@ -64,6 +64,14 @@ def _format(document, lines=None, options=None): 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, From cab01a137779425a4c624e0a6b90ad0f05741bf8 Mon Sep 17 00:00:00 2001 From: masfrost Date: Tue, 7 Dec 2021 23:55:00 -0800 Subject: [PATCH 5/6] another one --- pyls/plugins/yapf_format.py | 1 - test/plugins/test_yapf_format.py | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pyls/plugins/yapf_format.py b/pyls/plugins/yapf_format.py index fbf83cce..5287d257 100644 --- a/pyls/plugins/yapf_format.py +++ b/pyls/plugins/yapf_format.py @@ -13,7 +13,6 @@ def pyls_format_document(document, options=None): return _format(document, options=options) - @hookimpl def pyls_format_range(document, range, options=None): # pylint: disable=redefined-builtin # First we 'round' the range up/down to full lines only diff --git a/test/plugins/test_yapf_format.py b/test/plugins/test_yapf_format.py index ff479984..318aea69 100644 --- a/test/plugins/test_yapf_format.py +++ b/test/plugins/test_yapf_format.py @@ -45,6 +45,15 @@ def test_format_with_insert_spaces_option(workspace): assert len(res) == 1 assert res[0]['newText'] == FOUR_SPACE_DOC.replace(" ", "\t") +DOC_WITH_COMMENT = "x = 1# hello" + +def test_format_with_arbitrary_option(workspace): + doc = Document(DOC_URI, workspace, FOUR_SPACE_DOC) + res = pyls_format_document(doc, { "SPACES_BEFORE_COMMENT": 2 }) + + assert len(res) == 1 + assert res[0]['newText'] == FOUR_SPACE_DOC.replace("# hello", " # hello") + def test_range_format(workspace): doc = Document(DOC_URI, workspace, DOC) From 0dc5fcc1d8228d961c4d4754f39cd864772a1cf9 Mon Sep 17 00:00:00 2001 From: masfrost Date: Wed, 8 Dec 2021 00:11:37 -0800 Subject: [PATCH 6/6] Fix test --- test/plugins/test_yapf_format.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/plugins/test_yapf_format.py b/test/plugins/test_yapf_format.py index 318aea69..7a312449 100644 --- a/test/plugins/test_yapf_format.py +++ b/test/plugins/test_yapf_format.py @@ -45,14 +45,13 @@ def test_format_with_insert_spaces_option(workspace): assert len(res) == 1 assert res[0]['newText'] == FOUR_SPACE_DOC.replace(" ", "\t") -DOC_WITH_COMMENT = "x = 1# hello" -def test_format_with_arbitrary_option(workspace): +def test_format_with_yapf_specific_option(workspace): doc = Document(DOC_URI, workspace, FOUR_SPACE_DOC) - res = pyls_format_document(doc, { "SPACES_BEFORE_COMMENT": 2 }) + res = pyls_format_document(doc, { "USE_TABS": True }) assert len(res) == 1 - assert res[0]['newText'] == FOUR_SPACE_DOC.replace("# hello", " # hello") + assert res[0]['newText'] == FOUR_SPACE_DOC.replace(" ", "\t") def test_range_format(workspace):