-
Notifications
You must be signed in to change notification settings - Fork 182
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
Color Picker support in LSP #2053
Changes from all commits
07223ea
d34c06c
b5e292b
1c02247
e3de718
22c0aab
f26d2d9
113f339
23957e9
c464502
4fc9bcd
ea17403
b0da79c
fa50534
df001a4
751858b
ef59300
4c60249
09f8008
81fcfbf
03d3ea0
0d02c85
4754306
4ac908e
12fbff0
9f1a540
1661538
ab8352b
51eb05f
ebb9f4d
b1ac99b
7dd6a3b
8a1e5cd
ad43def
f06895c
a6d1e47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
from ..core.protocol import Color, TextEdit, ColorInformation | ||
from ..core.typing import Callable, Optional, List, Any | ||
from ..core.views import lsp_color_to_hex | ||
from ..formatting import apply_text_edits_to_view | ||
from abc import ABCMeta | ||
import os | ||
import sublime | ||
import sublime_plugin | ||
import subprocess | ||
import sys | ||
import threading | ||
|
||
OnPickCallback = Callable[[Optional[Color]], None] | ||
|
||
|
||
class ColorPickerPlugin(metaclass=ABCMeta): | ||
def pick(self, on_pick: OnPickCallback, preselect_color: Optional[Color] = None) -> None: | ||
... | ||
|
||
def normalize_color(self, color: Any) -> Optional[Color]: | ||
... | ||
|
||
def close(self) -> None: | ||
... | ||
|
||
|
||
if sublime.platform() == 'linux': | ||
class LinuxColorPicker(ColorPickerPlugin): | ||
process = None # type: Optional[subprocess.Popen] | ||
|
||
def pick(self, on_pick: OnPickCallback, preselect_color: Optional[Color] = None) -> None: | ||
self.close() | ||
t = threading.Thread(target=self._open_picker, args=(on_pick, preselect_color)) | ||
t.start() | ||
|
||
def _open_picker(self, on_pick: OnPickCallback, color: Optional[Color] = None) -> None: | ||
preselect_color_arg = "" | ||
if color: | ||
preselect_color_arg = "{},{},{},{}".format( | ||
color['red'], color['green'], color['blue'], color['alpha'] | ||
) | ||
picker_cmd = [ | ||
os.path.join(sublime.packages_path(), "LSP", "plugin", "color_picker", "linux_executable.py"), | ||
preselect_color_arg | ||
] | ||
self.process = subprocess.Popen(picker_cmd, stdout=subprocess.PIPE) | ||
output = self.process.communicate()[0].strip().decode('utf-8') | ||
on_pick(self.normalize_color(output)) | ||
|
||
def normalize_color(self, color: Any) -> Optional[Color]: | ||
if color and isinstance(color, str): | ||
r, g, b, a = map(float, color.split(',')) | ||
return { | ||
"red": r, | ||
"green": g, | ||
"blue": b, | ||
"alpha": a, | ||
} | ||
return None | ||
|
||
def close(self) -> None: | ||
if self.process: | ||
try: | ||
self.process.kill() | ||
except: | ||
pass | ||
self.process = None | ||
|
||
|
||
if sys.platform == "win32": | ||
import ctypes | ||
|
||
class CHOOSECOLOR(ctypes.Structure): | ||
_fields_ = [ | ||
("lStructSize", ctypes.c_uint32), | ||
("hwndOwner", ctypes.c_void_p), | ||
("hInstance", ctypes.c_void_p), | ||
("rgbResult", ctypes.c_uint32), | ||
("lpCustColors", ctypes.POINTER(ctypes.c_uint32)), | ||
("Flags", ctypes.c_uint32), | ||
("lCustData", ctypes.c_void_p), | ||
("lpfnHook", ctypes.c_void_p), | ||
("lpTemplateName", ctypes.c_wchar_p)] | ||
|
||
CC_SOLIDCOLOR = 0x80 | ||
CC_RGBINIT = 0x01 | ||
CC_FULLOPEN = 0x02 | ||
ChooseColorW = ctypes.windll.Comdlg32.ChooseColorW | ||
ChooseColorW.argtypes = [ctypes.POINTER(CHOOSECOLOR)] | ||
ChooseColorW.restype = ctypes.c_int32 | ||
|
||
|
||
class WindowsColorPicker(ColorPickerPlugin): | ||
def pick(self, on_pick: OnPickCallback, preselect_color: Optional[Color] = None) -> None: | ||
self.close() | ||
default_color = (255 << 16) | (255 << 8) | (255) | ||
if preselect_color: | ||
default_color = (round(255*preselect_color['blue']) << 16) | (round(255*preselect_color['green']) << 8) | round(255*preselect_color['red']) | ||
cc = CHOOSECOLOR() | ||
ctypes.memset(ctypes.byref(cc), 0, ctypes.sizeof(cc)) | ||
cc.lStructSize = ctypes.sizeof(cc) | ||
cc.hwndOwner = sublime.active_window().hwnd() | ||
CustomColors = ctypes.c_uint32 * 16 | ||
cc.lpCustColors = CustomColors() # uses 0 (black) for all 16 predefined custom colors | ||
cc.rgbResult = ctypes.c_uint32(default_color) | ||
cc.Flags = CC_SOLIDCOLOR | CC_FULLOPEN | CC_RGBINIT | ||
|
||
# ST window will become unresponsive until color picker dialog is closed | ||
output = ChooseColorW(ctypes.byref(cc)) | ||
|
||
if output == 1: # user clicked OK | ||
on_pick(self.normalize_color(cc.rgbResult)) | ||
else: | ||
on_pick(None) | ||
|
||
def normalize_color(self, bgr_color: Any) -> Optional[Color]: | ||
|
||
def bgr2color(bgr) -> Color: | ||
# 0x00BBGGRR | ||
byte_table = list(["{0:02X}".format(b) for b in range(256)]) | ||
b_hex = byte_table[(bgr >> 16) & 0xff] | ||
g_hex = byte_table[(bgr >> 8) & 0xff] | ||
r_hex = byte_table[(bgr) & 0xff] | ||
|
||
r = int(r_hex, 16) / 255 | ||
g = int(g_hex, 16) / 255 | ||
b = int(b_hex, 16) / 255 | ||
return { | ||
"red": r, | ||
"green": g, | ||
"blue": b, | ||
"alpha": 1 # Windows color picker doesn't support alpha, so fallback to 1 | ||
} | ||
|
||
if bgr_color: | ||
return bgr2color(bgr_color) | ||
return None | ||
|
||
def close(self) -> None: | ||
pass # on windows, the color picker will block until a color is choosen | ||
|
||
|
||
if sublime.platform() == 'osx': | ||
class OsxColorPicker(ColorPickerPlugin): | ||
process = None # type: Optional[subprocess.Popen] | ||
script = ''' | ||
function run(argv) {{ | ||
var app = Application.currentApplication(); | ||
app.includeStandardAdditions = true; | ||
var color = app.chooseColor({{defaultColor: [{red}, {green}, {blue}]}}); | ||
console.log(JSON.stringify(color)); | ||
}} | ||
''' | ||
|
||
def pick(self, on_pick: OnPickCallback, preselect_color: Optional[Color] = None) -> None: | ||
self.close() | ||
t = threading.Thread(target=self._open_picker, args=(on_pick, preselect_color)) | ||
t.start() | ||
|
||
def _open_picker(self, on_pick: OnPickCallback, color: Optional[Color] = None) -> None: | ||
script = self.script.format(red=0, green=0, blue=0) | ||
if color: | ||
script = self.script.format(red=color['red'], green=color['green'], blue=color['blue']) | ||
picker_cmd = ["/usr/bin/osascript", "-l", "JavaScript", "-e", script] | ||
self.process = subprocess.Popen(picker_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) | ||
output = self.process.communicate()[0].strip().decode('ascii') | ||
on_pick(self.normalize_color(output)) | ||
|
||
def normalize_color(self, color: Any) -> Optional[Color]: | ||
print('OSX output contains a warning:', color) | ||
if color and isinstance(color, list): | ||
r, g, b = 1, 1, 1 # hardcoded | ||
return { | ||
"red": r, | ||
"green": g, | ||
"blue": b, | ||
"alpha": 1, # OSX color picker doesn't support alpha, so fallback to 1 | ||
} | ||
return None | ||
|
||
def close(self) -> None: | ||
if self.process: | ||
try: | ||
self.process.kill() | ||
except: | ||
pass | ||
self.process = None | ||
|
||
|
||
def get_color_picker() -> Optional[ColorPickerPlugin]: | ||
if sublime.platform() == "linux": | ||
return LinuxColorPicker() | ||
if sublime.platform() == "windows": | ||
return WindowsColorPicker() | ||
if sublime.platform() == "osx": | ||
return OsxColorPicker() | ||
return None | ||
|
||
|
||
color_picker = get_color_picker() | ||
|
||
|
||
class CloseColorPickerOnBlur(sublime_plugin.EventListener): | ||
def on_activated(self, view: sublime.View) -> None: | ||
if color_picker: | ||
color_picker.close() | ||
|
||
def on_exit(self) -> None: | ||
if color_picker: | ||
color_picker.close() | ||
|
||
|
||
class LspChooseColorPicker(sublime_plugin.TextCommand): | ||
def run(self, edit: sublime.Edit, color_information: ColorInformation, file_name: str) -> None: | ||
if not color_picker: | ||
sublime.status_message('Your platform does not support a ColorPicker yet.') | ||
return | ||
window = self.view.window() | ||
if not window: | ||
return | ||
|
||
def on_select(color: Optional[Color]) -> None: | ||
self.on_pick_color(color, color_information, file_name) | ||
|
||
color_picker.pick(on_select, color_information['color']) | ||
|
||
def on_pick_color( | ||
self, selected_color: Optional[Color], color_information: ColorInformation, file_name: str | ||
) -> None: | ||
if not selected_color: | ||
return | ||
window = self.view.window() | ||
if not window: | ||
return | ||
view = window.find_open_file(file_name) | ||
new_text = lsp_color_to_hex(selected_color) | ||
text_edits = [{ | ||
"newText": new_text, | ||
"range": color_information['range'] | ||
}] # type: List[TextEdit] | ||
if view: | ||
apply_text_edits_to_view(text_edits, view) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/usr/bin/env python | ||
import gi | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume this package is not installed by default? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is installed by default in GNOME and other GTK distrons, That said, currently this script doesn't support all linux versions. Or something like ColorPicker, to have a fallback in the script |
||
import sys | ||
gi.require_version('Gtk', '3.0') | ||
from gi.repository import Gtk | ||
from gi.repository import Gdk | ||
|
||
color_chooser_dialog = Gtk.ColorChooserDialog(show_editor=True) | ||
color_chooser_dialog.set_title('LSP Color Picker') | ||
|
||
if len(sys.argv) > 1: # sys.argv[1] looks like '1,0.2,1,0.5' | ||
r, g, b, a = map(float, sys.argv[1].split(',')) | ||
preselect_color = Gdk.RGBA(r, g, b, a) | ||
color_chooser_dialog.set_rgba(preselect_color) | ||
|
||
def on_select_color(): | ||
color = color_chooser_dialog.get_rgba() | ||
print('{},{},{},{}'.format(color.red, color.green, color.blue, color.alpha)) | ||
|
||
if color_chooser_dialog.run() == Gtk.ResponseType.OK: | ||
on_select_color() | ||
|
||
color_chooser_dialog.destroy() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is shebang necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will check when I'm on linux.