Skip to content
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

Request color presentations when clicking on a color box #2065

Merged
merged 8 commits into from
Sep 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions boot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# Please keep this list sorted (Edit -> Sort Lines)
from .plugin.code_actions import LspCodeActionsCommand
from .plugin.code_lens import LspCodeLensCommand
from .plugin.color import LspColorPresentationCommand
from .plugin.completion import LspCommitCompletionWithOppositeInsertMode
from .plugin.completion import LspResolveDocsCommand
from .plugin.completion import LspSelectCompletionItemCommand
Expand Down
61 changes: 61 additions & 0 deletions plugin/color.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from .core.edit import parse_text_edit
from .core.protocol import ColorInformation
from .core.protocol import ColorPresentation
from .core.protocol import ColorPresentationParams
from .core.protocol import Request
from .core.registry import LspTextCommand
from .core.typing import List
from .core.views import range_to_region
from .core.views import text_document_identifier
import sublime


class LspColorPresentationCommand(LspTextCommand):

capability = 'colorProvider'

def run(self, edit: sublime.Edit, color_information: ColorInformation) -> None:
session = self.best_session(self.capability)
if session:
self._version = self.view.change_count()
self._range = color_information['range']
params = {
'textDocument': text_document_identifier(self.view),
'color': color_information['color'],
'range': self._range
} # type: ColorPresentationParams
session.send_request_async(Request.colorPresentation(params, self.view), self._handle_response_async)

def want_event(self) -> bool:
return False

def _handle_response_async(self, response: List[ColorPresentation]) -> None:
if not response:
return
window = self.view.window()
if not window:
return
if self._version != self.view.change_count():
return
old_text = self.view.substr(range_to_region(self._range, self.view))
self._filtered_response = [] # type: List[ColorPresentation]
for item in response:
# Filter out items that would apply no change
text_edit = item.get('textEdit')
if text_edit:
if text_edit['range'] == self._range and text_edit['newText'] == old_text:
rchl marked this conversation as resolved.
Show resolved Hide resolved
continue
elif item['label'] == old_text:
continue
self._filtered_response.append(item)
if self._filtered_response:
window.show_quick_panel(
[sublime.QuickPanelItem(item['label']) for item in self._filtered_response],
self._on_select,
placeholder="Change color format")

def _on_select(self, index: int) -> None:
if index > -1:
color_pres = self._filtered_response[index]
text_edit = color_pres.get('textEdit') or {'range': self._range, 'newText': color_pres['label']}
self.view.run_command('lsp_apply_document_edit', {'changes': [parse_text_edit(text_edit, self._version)]})
4 changes: 4 additions & 0 deletions plugin/core/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -5880,6 +5880,10 @@ def codeAction(cls, params: Mapping[str, Any], view: sublime.View) -> 'Request':
def documentColor(cls, params: Mapping[str, Any], view: sublime.View) -> 'Request':
return Request('textDocument/documentColor', params, view)

@classmethod
def colorPresentation(cls, params: ColorPresentationParams, view: sublime.View) -> 'Request':
return Request('textDocument/colorPresentation', params, view)

@classmethod
def willSaveWaitUntil(cls, params: Mapping[str, Any], view: sublime.View) -> 'Request':
return Request("textDocument/willSaveWaitUntil", params, view)
Expand Down
48 changes: 33 additions & 15 deletions plugin/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from .protocol import CodeActionContext
from .protocol import CodeActionParams
from .protocol import CodeActionTriggerKind
from .protocol import Color
from .protocol import ColorInformation
from .protocol import Command
from .protocol import CompletionItem
from .protocol import CompletionItemKind
Expand Down Expand Up @@ -781,28 +783,44 @@ def run(self, view_id: int, command: str, args: Optional[Dict[str, Any]] = None)


COLOR_BOX_HTML = """
<style>html {{padding: 0; background-color: transparent}}</style>
<style>
html {{
padding: 0;
background-color: transparent;
}}
a {{
display: inline-block;
height: 0.8rem;
predragnikolic marked this conversation as resolved.
Show resolved Hide resolved
width: 0.8rem;
margin-top: 0.1em;
border: 1px solid color(var(--foreground) alpha(0.25));
background-color: {color};
text-decoration: none;
}}
</style>
<body id='lsp-color-box'>
<div style='padding: 0.4em;
margin-top: 0.2em;
border: 1px solid color(var(--foreground) alpha(0.25));
background-color: rgba({}, {}, {}, {})'>
</div>
<a href='{command}'>&nbsp;</a>
</body>"""


def lsp_color_to_html(color_info: Dict[str, Any]) -> str:
color = color_info['color']
red = color['red'] * 255
green = color['green'] * 255
blue = color['blue'] * 255
alpha = color['alpha']
return COLOR_BOX_HTML.format(red, green, blue, alpha)
def color_to_hex(color: Color) -> str:
red = round(color['red'] * 255)
green = round(color['green'] * 255)
blue = round(color['blue'] * 255)
alpha_dec = color['alpha']
if alpha_dec < 1:
return "#{:02x}{:02x}{:02x}{:02x}".format(red, green, blue, round(alpha_dec * 255))
return "#{:02x}{:02x}{:02x}".format(red, green, blue)


def lsp_color_to_phantom(view: sublime.View, color_info: Dict[str, Any]) -> sublime.Phantom:
def lsp_color_to_html(view: sublime.View, color_info: ColorInformation) -> str:
command = sublime.command_url('lsp_color_presentation', {'color_information': color_info})
return COLOR_BOX_HTML.format(command=command, color=color_to_hex(color_info['color']))


def lsp_color_to_phantom(view: sublime.View, color_info: ColorInformation) -> sublime.Phantom:
region = range_to_region(color_info['range'], view)
return sublime.Phantom(region, lsp_color_to_html(color_info), sublime.LAYOUT_INLINE)
return sublime.Phantom(region, lsp_color_to_html(view, color_info), sublime.LAYOUT_INLINE)


def document_color_params(view: sublime.View) -> Dict[str, Any]:
Expand Down
9 changes: 6 additions & 3 deletions plugin/session_buffer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .core.panels import is_panel_open
from .core.panels import PanelName
from .core.protocol import ColorInformation
from .core.protocol import Diagnostic
from .core.protocol import DiagnosticSeverity
from .core.protocol import DocumentLink
Expand Down Expand Up @@ -353,9 +354,11 @@ def _do_color_boxes_async(self, view: sublime.View, version: int) -> None:
self._if_view_unchanged(self._on_color_boxes_async, version)
)

def _on_color_boxes_async(self, view: sublime.View, response: Any) -> None:
color_infos = response if response else []
self.color_phantoms.update([lsp_color_to_phantom(view, color_info) for color_info in color_infos])
def _on_color_boxes_async(self, view: sublime.View, response: List[ColorInformation]) -> None:
if response is None: # Guard against spec violation from certain language servers
self.color_phantoms.update([])
return
self.color_phantoms.update([lsp_color_to_phantom(view, color_info) for color_info in response])

# --- textDocument/documentLink ------------------------------------------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ def test_lsp_color_to_phantom(self) -> None:
}
]
phantom = lsp_color_to_phantom(self.view, response[0])
self.assertEqual(phantom.content, lsp_color_to_html(response[0]))
self.assertEqual(phantom.content, lsp_color_to_html(self.view, response[0]))
self.assertEqual(phantom.region, range_to_region(response[0]["range"], self.view))

def test_document_color_params(self) -> None:
Expand Down