diff --git a/pylsp/_utils.py b/pylsp/_utils.py index 9ac30cfc..0732067a 100644 --- a/pylsp/_utils.py +++ b/pylsp/_utils.py @@ -6,12 +6,17 @@ import logging import os import pathlib +import re import threading import jedi JEDI_VERSION = jedi.__version__ +# Eol chars accepted by the LSP protocol +EOL_CHARS = ['\r\n', '\r', '\n'] +EOL_REGEX = re.compile(f'({"|".join(EOL_CHARS)})') + log = logging.getLogger(__name__) @@ -220,3 +225,11 @@ def is_process_alive(pid): return e.errno == errno.EPERM else: return True + + +def get_eol_chars(text): + """Get EOL chars used in text.""" + match = EOL_REGEX.search(text) + if match: + return match.group(0) + return None diff --git a/pylsp/plugins/autopep8_format.py b/pylsp/plugins/autopep8_format.py index f62443f6..8915fb72 100644 --- a/pylsp/plugins/autopep8_format.py +++ b/pylsp/plugins/autopep8_format.py @@ -2,9 +2,12 @@ # Copyright 2021- Python Language Server Contributors. import logging + import pycodestyle from autopep8 import fix_code, continued_indentation as autopep8_c_i + from pylsp import hookimpl +from pylsp._utils import get_eol_chars log = logging.getLogger(__name__) @@ -38,15 +41,27 @@ def _format(config, document, line_range=None): del pycodestyle._checks['logical_line'][pycodestyle.continued_indentation] pycodestyle.register_check(autopep8_c_i) - new_source = fix_code(document.source, options=options) + # Autopep8 doesn't work with CR line endings, so we replace them by '\n' + # and restore them below. + replace_cr = False + source = document.source + eol_chars = get_eol_chars(source) + if eol_chars == '\r': + replace_cr = True + source = source.replace('\r', '\n') + + new_source = fix_code(source, options=options) # Switch it back del pycodestyle._checks['logical_line'][autopep8_c_i] pycodestyle.register_check(pycodestyle.continued_indentation) - if new_source == document.source: + if new_source == source: return [] + if replace_cr: + new_source = new_source.replace('\n', '\r') + # I'm too lazy at the moment to parse diffs into TextEdit items # So let's just return the entire file... return [{ diff --git a/pylsp/plugins/yapf_format.py b/pylsp/plugins/yapf_format.py index 6728007b..1c90f965 100644 --- a/pylsp/plugins/yapf_format.py +++ b/pylsp/plugins/yapf_format.py @@ -3,9 +3,12 @@ import logging import os + from yapf.yapflib import file_resources from yapf.yapflib.yapf_api import FormatCode + from pylsp import hookimpl +from pylsp._utils import get_eol_chars log = logging.getLogger(__name__) @@ -34,8 +37,17 @@ def pylsp_format_range(document, range): # pylint: disable=redefined-builtin def _format(document, lines=None): + # Yapf doesn't work with CR line endings, so we replace them by '\n' + # and restore them below. + replace_cr = False + source = document.source + eol_chars = get_eol_chars(source) + if eol_chars == '\r': + replace_cr = True + source = source.replace('\r', '\n') + new_source, changed = FormatCode( - document.source, + source, lines=lines, filename=document.filename, style_config=file_resources.GetDefaultStyleForDir( @@ -46,6 +58,9 @@ def _format(document, lines=None): if not changed: return [] + if replace_cr: + new_source = new_source.replace('\n', '\r') + # I'm too lazy at the moment to parse diffs into TextEdit items # So let's just return the entire file... return [{ diff --git a/test/plugins/test_autopep8_format.py b/test/plugins/test_autopep8_format.py index cfb7bb60..1e790aaa 100644 --- a/test/plugins/test_autopep8_format.py +++ b/test/plugins/test_autopep8_format.py @@ -71,3 +71,10 @@ def test_hanging_indentation(config, workspace): assert len(res) == 1 assert res[0]['newText'] == CORRECT_INDENTED_DOC + + +def test_cr_line_endings(config, workspace): + doc = Document(DOC_URI, workspace, 'import os;import sys\r\rdict(a=1)') + res = pylsp_format_document(config, doc) + + assert res[0]['newText'] == 'import os\rimport sys\r\rdict(a=1)\r' diff --git a/test/plugins/test_yapf_format.py b/test/plugins/test_yapf_format.py index 90410432..4346985c 100644 --- a/test/plugins/test_yapf_format.py +++ b/test/plugins/test_yapf_format.py @@ -58,3 +58,10 @@ def test_config_file(tmpdir, workspace): # A was split on multiple lines because of column_limit from config file assert pylsp_format_document(doc)[0]['newText'] == "A = [\n 'h', 'w',\n 'a'\n]\n\nB = ['h', 'w']\n" + + +def test_cr_line_endings(workspace): + doc = Document(DOC_URI, workspace, 'import os;import sys\r\rdict(a=1)') + res = pylsp_format_document(doc) + + assert res[0]['newText'] == 'import os\rimport sys\r\rdict(a=1)\r'