Skip to content

Commit

Permalink
Merge branch 'master' into refactor/move-window-logmessage-to-the-res…
Browse files Browse the repository at this point in the history
…t-of-the-notification-handlers
  • Loading branch information
rwols committed Apr 13, 2019
2 parents e9ce28d + 33c07f7 commit 5e93a5f
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 74 deletions.
7 changes: 7 additions & 0 deletions LSP.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,13 @@
"syntaxes": ["Packages/Go/Go.sublime-syntax"],
"languageId": "go"
},
"gopls":
{
"command": ["gopls"],
"scopes": ["source.go"],
"syntaxes": ["Packages/Go/Go.sublime-syntax"],
"languageId": "go"
},
"jdtls":
{
"command": ["java", "-jar", "PATH_TO_JDT_SERVER/plugins/org.eclipse.equinox.launcher_1.4.0.v20161219-1356.jar", "-configuration", "PATH_TO_CONFIG_DIR"],
Expand Down
66 changes: 30 additions & 36 deletions plugin/core/edit.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import os
import sublime
import sublime_plugin
from timeit import default_timer as timer

try:
from typing import List, Dict, Optional
assert List and Dict and Optional
from typing import List, Dict, Optional, Any
assert List and Dict and Optional and Any
except ImportError:
pass

Expand Down Expand Up @@ -55,41 +56,29 @@ def open_and_apply_edits(self, path, file_changes):

class LspApplyDocumentEditCommand(sublime_plugin.TextCommand):
def run(self, edit, changes: 'Optional[List[dict]]' = None, show_status=True):
# Apply the changes in reverse, so that we don't invalidate the range
# of any change that we haven't applied yet.
start = timer()
changes2 = changes or [] # New variable of type List[dict]
indices = self.changes_order(changes2)
for index in indices:
change = changes2[index]
self.apply_change(self.create_region(change), change.get('newText'), edit)
elapsed = timer() - start

# Sort changes due to issues with self.view.get_regions
# See https://github.com/tomv564/LSP/issues/325
changes = self.changes_sorted(changes) if changes else []
regions = list(self.create_region(change) for change in changes)
replacements = list(change.get('newText') for change in changes)

# TODO why source.python here?
self.view.add_regions('lsp_edit', regions, "source.python")

index = 0
last_region_count = len(regions)
for newText in replacements:
# refresh updated regions after each edit.
updated_regions = self.view.get_regions('lsp_edit')
region = updated_regions[index] #
self.apply_change(region, newText, edit)
if len(self.view.get_regions('lsp_edit')) == last_region_count and last_region_count > 1:
index += 1 # no regions lost, move to next region.
else:
# current region was removed, don't advance index.
last_region_count = len(self.view.get_regions('lsp_edit'))

self.view.erase_regions('lsp_edit')
if show_status:
window = self.view.window()
if window:
base_dir = get_project_path(window)
file_path = self.view.file_name()
relative_file_path = os.path.relpath(file_path, base_dir) if base_dir else file_path
message = 'Applied {} change(s) to {}'.format(len(changes), relative_file_path)
message = 'Applied {} change(s) to {} in {:.1f} ms'.format(
len(indices), relative_file_path, elapsed * 1000)
window.status_message(message)
debug(message)

def changes_sorted(self, changes: 'List[dict]') -> 'List[Dict]':
# changes looks like this:
def changes_order(self, changes: 'List[dict]') -> 'List[int]':
# Changes look like this:
# [
# {
# 'newText': str,
Expand All @@ -100,16 +89,21 @@ def changes_sorted(self, changes: 'List[dict]') -> 'List[Dict]':
# }
# ]

# Maps a change to the tuple (range.start.line, range.start.character)
def get_start_position(change):
r = change.get('range')
start = r.get('start')
def get_start_position(index: int):
change = changes[index] # type: Any
start = change.get('range').get('start')
line = start.get('line')
character = start.get('character')
return (line, character) # Return tuple so comparing/sorting tuples in the form of (1, 2)

# Sort by start position
return sorted(changes, key=get_start_position)
return (line, character, index)

# The spec reads:
# > However, it is possible that multiple edits have the same start position: multiple
# > inserts, or any number of inserts followed by a single remove or replace edit. If
# > multiple inserts have the same position, the order in the array defines the order in
# > which the inserted strings appear in the resulting text.
# So we sort by start position. But if multiple text edits start at the same position,
# we use the index in the array as the key.
return sorted(range(len(changes)), key=get_start_position, reverse=True)

def create_region(self, change):
return range_to_region(Range.from_lsp(change['range']), self.view)
Expand Down
90 changes: 53 additions & 37 deletions tests/test_edit.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,69 @@
from unittesting import DeferrableTestCase
import sublime
from os.path import dirname

test_file_path = dirname(__file__) + "/testfile.txt"

ORIGINAL_CONTENT = """<dom-module id="some-thing">
<style></style>
<template>
</template>
</dom-module>"""

EXPECTED_CONTENT = """<dom-module id="some-thing">
<template>
<style></style>
</template>
</dom-module>"""
def text_edit(Start: 'Tuple[int, int]', End: 'Tuple[int, int]', NewText: str):
return {
'range': {
'start': {'line': Start[0], 'character': Start[1]},
'end': {'line': End[0], 'character': End[1]},
},
'newText': NewText,
}


class ApplyDocumentEditTests(DeferrableTestCase):

def setUp(self):
self.view = sublime.active_window().new_file()
self.view.run_command('insert', {"characters": ORIGINAL_CONTENT})

def test_apply(self):
file_changes = [{
'range': {
'start': {'line': 0, 'character': 28},
'end': {'line': 1, 'character': 0}
},
'newText': ''
}, {
'range': {
'start': {'line': 1, 'character': 0},
'end': {'line': 1, 'character': 15}
},
'newText': ''
}, {
'range': {
'start': {'line': 2, 'character': 10},
'end': {'line': 2, 'character': 10}
},
'newText': '\n <style></style>'
}]

self.view.run_command('lsp_apply_document_edit', {'changes': file_changes, 'show_status': False})
original = (
'<dom-module id="some-thing">\n'
'<style></style>\n'
'<template>\n'
'</template>\n'
'</dom-module>\n'
)
file_changes = [
text_edit((0, 28), (1, 0), ''), # delete first \n
text_edit((1, 0), (1, 15), ''), # delete second line (but not the \n)
text_edit((2, 10), (2, 10), '\n <style></style>'), # insert after <template>
]
expected = (
'<dom-module id="some-thing">\n'
'<template>\n'
' <style></style>\n'
'</template>\n'
'</dom-module>\n'
)
self.run_test(original, expected, file_changes)

def test_apply_and_preserve_order(self):
original = (
'abcde\n'
'fghij\n'
)
# Note that (1, 2) comes before (0, 1) in the text.
file_changes = [
text_edit((1, 2), (1, 2), '4'), # insert after the g
text_edit((1, 2), (1, 2), '5'),
text_edit((1, 2), (1, 3), '6'), # replace the h
text_edit((0, 1), (0, 1), '1'), # insert after a
text_edit((0, 1), (0, 1), '2'),
text_edit((0, 1), (0, 1), '3'),
]
expected = (
'a123bcde\n'
'fg456ij\n'
)
self.run_test(original, expected, file_changes)

def run_test(self, original: str, expected: str, file_changes):
self.view.run_command('insert', {"characters": original})
self.view.run_command(
'lsp_apply_document_edit', {'changes': file_changes, 'show_status': False})
edited_content = self.view.substr(sublime.Region(0, self.view.size()))
self.assertEquals(edited_content, EXPECTED_CONTENT)
self.assertEquals(edited_content, expected)

def tearDown(self):
if self.view:
Expand Down
1 change: 0 additions & 1 deletion tests/testfile.txt

This file was deleted.

0 comments on commit 5e93a5f

Please sign in to comment.