Compare commits
1 Commits
develop
...
619fae8a20
| Author | SHA1 | Date | |
|---|---|---|---|
| 619fae8a20 |
@@ -3,13 +3,11 @@ A template project for Python with Gtk applications.
|
|||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
* PyGObject (Gtk introspection library)
|
* PyGObject (Gtk introspection library)
|
||||||
* pygobject-stubs (For actually getting pylsp or python-language-server to auto complete in LSPs. Do if GTK3 --no-cache-dir --config-settings=config=Gtk3,Gdk3,Soup2)
|
|
||||||
* pyxdg (Desktop ".desktop" file parser)
|
* pyxdg (Desktop ".desktop" file parser)
|
||||||
* setproctitle (Define process title to search and kill more easily)
|
* setproctitle (Define process title to search and kill more easily)
|
||||||
* sqlmodel (SQL databases and is powered by Pydantic and SQLAlchemy)
|
* sqlmodel (SQL databases and is powered by Pydantic and SQLAlchemy)
|
||||||
|
|
||||||
### Note
|
### Note
|
||||||
* pyrightconfig.json can prompt IDEs that use pyright lsp on where imports are located- look at venvPath and venv. "venvPath" is parent path of "venv" where "venv" is just the name of the folder under the parent path that is the python created venv.
|
|
||||||
* Move respetive sub folder content under user_config to the same places in Linux. Though, user/share/<app name> can go to ~/.config folder if prefered.
|
* Move respetive sub folder content under user_config to the same places in Linux. Though, user/share/<app name> can go to ~/.config folder if prefered.
|
||||||
* In additiion, place the plugins folder in the same app folder you moved to /usr/share/<app name> or ~/.config/<app name> .
|
* In additiion, place the plugins folder in the same app folder you moved to /usr/share/<app name> or ~/.config/<app name> .
|
||||||
There are a "\<change_me\>" strings and files that need to be set according to your app's name located at:
|
There are a "\<change_me\>" strings and files that need to be set according to your app's name located at:
|
||||||
@@ -22,4 +20,4 @@ There are a "\<change_me\>" strings and files that need to be set according to y
|
|||||||
|
|
||||||
|
|
||||||
For the user_config, after changing names and files, copy all content to their respective destinations.
|
For the user_config, after changing names and files, copy all content to their respective destinations.
|
||||||
The logic follows Debian Dpkg packaging and its placement logic.
|
The logic follows Debian Dpkg packaging and its placement logic.
|
||||||
2
plugins/README.txt
Normal file
2
plugins/README.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
### Note
|
||||||
|
Copy the example and rename it to your desired name. The Main class and passed in arguments are required. You don't necessarily need to use the passed in socket_id or event_system.
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Plugin Module
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Plugin Package
|
|
||||||
"""
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Autopairs:
|
|
||||||
def __init__(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def handle_word_wrap(self, buffer, char_str: str):
|
|
||||||
wrap_block = self.get_wrap_block(char_str)
|
|
||||||
if not wrap_block: return
|
|
||||||
|
|
||||||
selection = buffer.get_selection_bounds()
|
|
||||||
if not selection:
|
|
||||||
self.insert_pair(buffer, char_str, wrap_block)
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.wrap_selection(buffer, char_str, wrap_block, selection)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def insert_pair(
|
|
||||||
self, buffer, char_str: str, wrap_block: tuple
|
|
||||||
):
|
|
||||||
buffer.begin_user_action()
|
|
||||||
|
|
||||||
left_block, right_block = wrap_block
|
|
||||||
insert_mark = buffer.get_insert()
|
|
||||||
|
|
||||||
insert_itr = buffer.get_iter_at_mark(insert_mark)
|
|
||||||
buffer.insert(insert_itr, f"{left_block}{right_block}")
|
|
||||||
insert_itr = buffer.get_iter_at_mark( insert_mark )
|
|
||||||
insert_itr.backward_char()
|
|
||||||
|
|
||||||
buffer.place_cursor(insert_itr)
|
|
||||||
|
|
||||||
buffer.end_user_action()
|
|
||||||
|
|
||||||
def wrap_selection(
|
|
||||||
self, buffer, char_str: str, wrap_block: tuple, selection
|
|
||||||
):
|
|
||||||
left_block, \
|
|
||||||
right_block = wrap_block
|
|
||||||
start_itr, \
|
|
||||||
end_itr = selection
|
|
||||||
data = buffer.get_text(
|
|
||||||
start_itr, end_itr, include_hidden_chars = False
|
|
||||||
)
|
|
||||||
start_mark = buffer.create_mark("startclose", start_itr, False)
|
|
||||||
end_mark = buffer.create_mark("endclose", end_itr, True)
|
|
||||||
|
|
||||||
buffer.begin_user_action()
|
|
||||||
|
|
||||||
buffer.insert(start_itr, left_block)
|
|
||||||
end_itr = buffer.get_iter_at_mark(end_mark)
|
|
||||||
buffer.insert(end_itr, right_block)
|
|
||||||
|
|
||||||
start = buffer.get_iter_at_mark(start_mark)
|
|
||||||
end = buffer.get_iter_at_mark(end_mark)
|
|
||||||
|
|
||||||
buffer.select_range(start, end)
|
|
||||||
buffer.delete_mark_by_name("startclose")
|
|
||||||
buffer.delete_mark_by_name("endclose")
|
|
||||||
|
|
||||||
buffer.end_user_action()
|
|
||||||
|
|
||||||
def get_wrap_block(self, char_str) -> tuple:
|
|
||||||
left_block = ""
|
|
||||||
right_block = ""
|
|
||||||
|
|
||||||
match char_str:
|
|
||||||
case "(" | ")":
|
|
||||||
left_block = "("
|
|
||||||
right_block = ")"
|
|
||||||
case "[" | "]":
|
|
||||||
left_block = "["
|
|
||||||
right_block = "]"
|
|
||||||
case "{" | "}":
|
|
||||||
left_block = "{"
|
|
||||||
right_block = "}"
|
|
||||||
case '"':
|
|
||||||
left_block = '"'
|
|
||||||
right_block = '"'
|
|
||||||
case "'":
|
|
||||||
left_block = "'"
|
|
||||||
right_block = "'"
|
|
||||||
case "`":
|
|
||||||
left_block = "`"
|
|
||||||
right_block = "`"
|
|
||||||
case _:
|
|
||||||
return ()
|
|
||||||
|
|
||||||
return left_block, right_block
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Autopairs",
|
|
||||||
"author": "ITDominator",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"support": "",
|
|
||||||
"requests": {}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
|
||||||
|
|
||||||
from .autopairs import Autopairs
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
autopairs = Autopairs()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginCode):
|
|
||||||
def __init__(self):
|
|
||||||
super(Plugin, self).__init__()
|
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
event = Event_Factory.create_event("register_command",
|
|
||||||
command_name = "autopairs",
|
|
||||||
command = Handler,
|
|
||||||
binding_mode = "held",
|
|
||||||
binding = [
|
|
||||||
"'", "`", "[", "]",
|
|
||||||
'<Shift>"',
|
|
||||||
'<Shift>(',
|
|
||||||
'<Shift>)',
|
|
||||||
'<Shift>{',
|
|
||||||
'<Shift>}'
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.emit_to("source_views", event)
|
|
||||||
|
|
||||||
def unload(self):
|
|
||||||
event = Event_Factory.create_event("unregister_command",
|
|
||||||
command_name = "autopairs",
|
|
||||||
command = Handler,
|
|
||||||
binding_mode = "held",
|
|
||||||
binding = [
|
|
||||||
"'", "`", "[", "]",
|
|
||||||
'<Shift>"',
|
|
||||||
'<Shift>(',
|
|
||||||
'<Shift>)',
|
|
||||||
'<Shift>{',
|
|
||||||
'<Shift>}'
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.emit_to("source_views", event)
|
|
||||||
autopairs = None
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class Handler:
|
|
||||||
@staticmethod
|
|
||||||
def execute(
|
|
||||||
view: any,
|
|
||||||
char_str: str,
|
|
||||||
*args,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
logger.debug("Command: Autopairs")
|
|
||||||
autopairs.handle_word_wrap(view.get_buffer(), char_str)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Plugin Module
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Plugin Package
|
|
||||||
"""
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Autopairs:
|
|
||||||
def __init__(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def handle_word_wrap(self, buffer, char_str: str):
|
|
||||||
wrap_block = self.get_wrap_block(char_str)
|
|
||||||
if not wrap_block: return
|
|
||||||
|
|
||||||
selection = buffer.get_selection_bounds()
|
|
||||||
if not selection:
|
|
||||||
self.insert_pair(buffer, char_str, wrap_block)
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.wrap_selection(buffer, char_str, wrap_block, selection)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def insert_pair(
|
|
||||||
self, buffer, char_str: str, wrap_block: tuple
|
|
||||||
):
|
|
||||||
buffer.begin_user_action()
|
|
||||||
|
|
||||||
left_block, right_block = wrap_block
|
|
||||||
insert_mark = buffer.get_insert()
|
|
||||||
|
|
||||||
insert_itr = buffer.get_iter_at_mark(insert_mark)
|
|
||||||
buffer.insert(insert_itr, f"{left_block}{right_block}")
|
|
||||||
insert_itr = buffer.get_iter_at_mark( insert_mark )
|
|
||||||
insert_itr.backward_char()
|
|
||||||
|
|
||||||
buffer.place_cursor(insert_itr)
|
|
||||||
|
|
||||||
buffer.end_user_action()
|
|
||||||
|
|
||||||
def wrap_selection(
|
|
||||||
self, buffer, char_str: str, wrap_block: tuple, selection
|
|
||||||
):
|
|
||||||
left_block, \
|
|
||||||
right_block = wrap_block
|
|
||||||
start_itr, \
|
|
||||||
end_itr = selection
|
|
||||||
data = buffer.get_text(
|
|
||||||
start_itr, end_itr, include_hidden_chars = False
|
|
||||||
)
|
|
||||||
start_mark = buffer.create_mark("startclose", start_itr, False)
|
|
||||||
end_mark = buffer.create_mark("endclose", end_itr, True)
|
|
||||||
|
|
||||||
buffer.begin_user_action()
|
|
||||||
|
|
||||||
buffer.insert(start_itr, left_block)
|
|
||||||
end_itr = buffer.get_iter_at_mark(end_mark)
|
|
||||||
buffer.insert(end_itr, right_block)
|
|
||||||
|
|
||||||
start = buffer.get_iter_at_mark(start_mark)
|
|
||||||
end = buffer.get_iter_at_mark(end_mark)
|
|
||||||
|
|
||||||
buffer.select_range(start, end)
|
|
||||||
buffer.delete_mark_by_name("startclose")
|
|
||||||
buffer.delete_mark_by_name("endclose")
|
|
||||||
|
|
||||||
buffer.end_user_action()
|
|
||||||
|
|
||||||
def get_wrap_block(self, char_str) -> tuple:
|
|
||||||
left_block = ""
|
|
||||||
right_block = ""
|
|
||||||
|
|
||||||
match char_str:
|
|
||||||
case "(" | ")":
|
|
||||||
left_block = "("
|
|
||||||
right_block = ")"
|
|
||||||
case "[" | "]":
|
|
||||||
left_block = "["
|
|
||||||
right_block = "]"
|
|
||||||
case "{" | "}":
|
|
||||||
left_block = "{"
|
|
||||||
right_block = "}"
|
|
||||||
case '"':
|
|
||||||
left_block = '"'
|
|
||||||
right_block = '"'
|
|
||||||
case "'":
|
|
||||||
left_block = "'"
|
|
||||||
right_block = "'"
|
|
||||||
case "`":
|
|
||||||
left_block = "`"
|
|
||||||
right_block = "`"
|
|
||||||
case _:
|
|
||||||
return ()
|
|
||||||
|
|
||||||
return left_block, right_block
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "File History",
|
|
||||||
"author": "ITDominator",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"support": "",
|
|
||||||
"requests": {}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
history: list = []
|
|
||||||
history_size: int = 30
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginCode):
|
|
||||||
def __init__(self):
|
|
||||||
super(Plugin, self).__init__()
|
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
|
||||||
if isinstance(event, Code_Event_Types.RemovedFileEvent):
|
|
||||||
if event.file.ftype == "buffer": return
|
|
||||||
|
|
||||||
if len(history) == history_size:
|
|
||||||
history.pop(0)
|
|
||||||
|
|
||||||
history.append(event.file.fpath)
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
self._manage_signals("register_command")
|
|
||||||
|
|
||||||
def unload(self):
|
|
||||||
self._manage_signals("unregister_command")
|
|
||||||
|
|
||||||
def _manage_signals(self, action: str):
|
|
||||||
event = Event_Factory.create_event(action,
|
|
||||||
command_name = "file_history_pop",
|
|
||||||
command = Handler,
|
|
||||||
binding_mode = "released",
|
|
||||||
binding = "<Shift><Control>t"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.emit_to("source_views", event)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class Handler:
|
|
||||||
@staticmethod
|
|
||||||
def execute(
|
|
||||||
view: any,
|
|
||||||
char_str: str,
|
|
||||||
*args,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
logger.debug("Command: File History")
|
|
||||||
if len(history) == 0: return
|
|
||||||
|
|
||||||
view._on_uri_data_received(
|
|
||||||
[
|
|
||||||
f"file://{history.pop()}"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from .helpers import clear_temp_cut_buffer_delayed, set_temp_cut_buffer_delayed
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Handler:
|
|
||||||
@staticmethod
|
|
||||||
def execute(
|
|
||||||
view: GtkSource.View,
|
|
||||||
*args,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
logger.debug("Command: Cut to Temp Buffer")
|
|
||||||
|
|
||||||
clear_temp_cut_buffer_delayed(view)
|
|
||||||
|
|
||||||
buffer = view.get_buffer()
|
|
||||||
itr = buffer.get_iter_at_mark(buffer.get_insert())
|
|
||||||
|
|
||||||
start_itr = itr.copy()
|
|
||||||
start_itr.set_line_offset(0)
|
|
||||||
|
|
||||||
end_itr = start_itr.copy()
|
|
||||||
if not end_itr.forward_line():
|
|
||||||
end_itr = buffer.get_end_iter()
|
|
||||||
|
|
||||||
if not hasattr(view, "_cut_buffer"):
|
|
||||||
view._cut_buffer = ""
|
|
||||||
|
|
||||||
line_str = buffer.get_text(start_itr, end_itr, True)
|
|
||||||
view._cut_buffer += line_str
|
|
||||||
|
|
||||||
buffer.delete(start_itr, end_itr)
|
|
||||||
|
|
||||||
set_temp_cut_buffer_delayed(view)
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
from gi.repository import GLib
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def clear_temp_cut_buffer_delayed(view: any):
|
|
||||||
if not hasattr(view, "_cut_temp_timeout_id"): return
|
|
||||||
if not view._cut_temp_timeout_id: return
|
|
||||||
|
|
||||||
GLib.source_remove(view._cut_temp_timeout_id)
|
|
||||||
|
|
||||||
def set_temp_cut_buffer_delayed(view: any):
|
|
||||||
def clear_temp_buffer(view: any):
|
|
||||||
view._cut_buffer = ""
|
|
||||||
view._cut_temp_timeout_id = None
|
|
||||||
return False
|
|
||||||
|
|
||||||
view._cut_temp_timeout_id = GLib.timeout_add(15000, clear_temp_buffer, view)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Nanoesq Temp Buffer",
|
|
||||||
"author": "ITDominator",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"support": "",
|
|
||||||
"requests": {}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GLib
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from .helpers import clear_temp_cut_buffer_delayed, set_temp_cut_buffer_delayed
|
|
||||||
|
|
||||||
|
|
||||||
class Handler2:
|
|
||||||
@staticmethod
|
|
||||||
def execute(
|
|
||||||
view: GtkSource.View,
|
|
||||||
*args,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
logger.debug("Command: Paste Temp Buffer")
|
|
||||||
if not hasattr(view, "_cut_temp_timeout_id"): return
|
|
||||||
if not hasattr(view, "_cut_buffer"): return
|
|
||||||
if not view._cut_buffer: return
|
|
||||||
|
|
||||||
clear_temp_cut_buffer_delayed(view)
|
|
||||||
|
|
||||||
buffer = view.get_buffer()
|
|
||||||
itr = buffer.get_iter_at_mark( buffer.get_insert() )
|
|
||||||
insert_itr = itr.copy()
|
|
||||||
|
|
||||||
buffer.insert(insert_itr, view._cut_buffer, -1)
|
|
||||||
|
|
||||||
set_temp_cut_buffer_delayed(view)
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
|
||||||
|
|
||||||
from .cut_to_temp_buffer import Handler
|
|
||||||
from .paste_temp_buffer import Handler2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginCode):
|
|
||||||
def __init__(self):
|
|
||||||
super(Plugin, self).__init__()
|
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
self._manage_signals("register_command")
|
|
||||||
|
|
||||||
def unload(self):
|
|
||||||
self._manage_signals("unregister_command")
|
|
||||||
|
|
||||||
def _manage_signals(self, action: str):
|
|
||||||
event = Event_Factory.create_event(action,
|
|
||||||
command_name = "cut_to_temp_buffer",
|
|
||||||
command = Handler,
|
|
||||||
binding_mode = "held",
|
|
||||||
binding = "<Control>k"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.emit_to("source_views", event)
|
|
||||||
|
|
||||||
event = Event_Factory.create_event(action,
|
|
||||||
command_name = "paste_temp_buffer",
|
|
||||||
command = Handler2,
|
|
||||||
binding_mode = "held",
|
|
||||||
binding = "<Control>u"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.emit_to("source_views", event)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
...
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Module
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Package
|
|
||||||
"""
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
emit_to: callable = None
|
|
||||||
|
|
||||||
def execute(
|
|
||||||
source_view,
|
|
||||||
char_str,
|
|
||||||
modkeys_states
|
|
||||||
):
|
|
||||||
logger.debug("Command: Close Split Pane")
|
|
||||||
|
|
||||||
scrolled_win = source_view.get_parent()
|
|
||||||
pane = scrolled_win.get_parent()
|
|
||||||
|
|
||||||
if not isinstance(pane, Gtk.Paned): return
|
|
||||||
|
|
||||||
container = pane.get_parent()
|
|
||||||
source_view1 = pane.get_child1()
|
|
||||||
source_view2 = pane.get_child2()
|
|
||||||
|
|
||||||
if scrolled_win == source_view1:
|
|
||||||
remaining = source_view2
|
|
||||||
closing_view = source_view
|
|
||||||
else:
|
|
||||||
remaining = source_view1
|
|
||||||
closing_view = source_view
|
|
||||||
|
|
||||||
remaining_view = remaining.get_child()
|
|
||||||
left = closing_view.sibling_left
|
|
||||||
right = closing_view.sibling_right
|
|
||||||
|
|
||||||
if left:
|
|
||||||
left.sibling_right = right
|
|
||||||
|
|
||||||
if right:
|
|
||||||
right.sibling_left = left
|
|
||||||
|
|
||||||
pane.remove(source_view1)
|
|
||||||
pane.remove(source_view2)
|
|
||||||
|
|
||||||
container.remove(pane)
|
|
||||||
container.add(remaining)
|
|
||||||
|
|
||||||
event = Event_Factory.create_event(
|
|
||||||
"remove_source_view",
|
|
||||||
view = closing_view
|
|
||||||
)
|
|
||||||
emit_to("source_views", event)
|
|
||||||
|
|
||||||
remaining_view.grab_focus()
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
|
||||||
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
from libs.dto.states import SourceViewStates
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
emit_to: callable = None
|
|
||||||
|
|
||||||
def execute(
|
|
||||||
source_view1,
|
|
||||||
char_str,
|
|
||||||
modkeys_states
|
|
||||||
):
|
|
||||||
logger.debug("Command: Split Pane")
|
|
||||||
|
|
||||||
scrolled_win1 = source_view1.get_parent()
|
|
||||||
container = scrolled_win1.get_parent()
|
|
||||||
pane = Gtk.Paned()
|
|
||||||
event = Event_Factory.create_event(
|
|
||||||
"create_source_view",
|
|
||||||
state = SourceViewStates.INSERT
|
|
||||||
)
|
|
||||||
emit_to("source_views", event)
|
|
||||||
|
|
||||||
scrolled_win2, \
|
|
||||||
source_view2 = event.response
|
|
||||||
old_sibling_right = None
|
|
||||||
|
|
||||||
if source_view1.sibling_right:
|
|
||||||
old_sibling_right = source_view1.sibling_right
|
|
||||||
|
|
||||||
source_view1.sibling_right = source_view2
|
|
||||||
if old_sibling_right:
|
|
||||||
old_sibling_right.sibling_left = source_view2
|
|
||||||
source_view2.sibling_right = old_sibling_right
|
|
||||||
|
|
||||||
source_view2.sibling_left = source_view1
|
|
||||||
|
|
||||||
pane.set_hexpand(True)
|
|
||||||
pane.set_vexpand(True)
|
|
||||||
pane.set_wide_handle(True)
|
|
||||||
|
|
||||||
container.remove(scrolled_win1)
|
|
||||||
pane.pack1( scrolled_win1, True, True )
|
|
||||||
pane.pack2( scrolled_win2, True, True )
|
|
||||||
container.add(pane)
|
|
||||||
|
|
||||||
pane.show_all()
|
|
||||||
|
|
||||||
is_control, is_shift, is_alt = modkeys_states
|
|
||||||
if is_control and is_shift:
|
|
||||||
pane.set_orientation(Gtk.Orientation.VERTICAL)
|
|
||||||
elif is_control:
|
|
||||||
pane.set_orientation(Gtk.Orientation.HORIZONTAL)
|
|
||||||
|
|
||||||
source_view2.grab_focus()
|
|
||||||
source_view2.command.exec("new_file")
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def execute(
|
|
||||||
view: GtkSource.View,
|
|
||||||
*args,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
logger.debug("Command: Focus Left Sibling")
|
|
||||||
if not view.sibling_left: return
|
|
||||||
view.sibling_left.grab_focus()
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def execute(
|
|
||||||
view: GtkSource.View,
|
|
||||||
*args,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
logger.debug("Command: Focus Right Sibling")
|
|
||||||
if not view.sibling_right: return
|
|
||||||
view.sibling_right.grab_focus()
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Split Pane Manager",
|
|
||||||
"author": "ITDominator",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"support": "",
|
|
||||||
"requests": {}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def execute(
|
|
||||||
view: GtkSource.View,
|
|
||||||
*args,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
logger.debug("Command: Move To Left Sibling")
|
|
||||||
if not view.sibling_left: return
|
|
||||||
|
|
||||||
buffer = view.get_buffer()
|
|
||||||
popped_file, next_file = view.command.get_swap_file(view)
|
|
||||||
|
|
||||||
view.sibling_left.set_buffer(buffer)
|
|
||||||
view.sibling_left.get_parent().show()
|
|
||||||
view.sibling_left.grab_focus()
|
|
||||||
|
|
||||||
if next_file:
|
|
||||||
view.set_buffer(next_file.buffer)
|
|
||||||
else:
|
|
||||||
view.command.exec("new_file")
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def execute(
|
|
||||||
view: GtkSource.View,
|
|
||||||
*args,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
logger.debug("Command: Move To Right Sibling")
|
|
||||||
if not view.sibling_right: return
|
|
||||||
|
|
||||||
buffer = view.get_buffer()
|
|
||||||
popped_file, next_file = view.command.get_swap_file(view)
|
|
||||||
|
|
||||||
view.sibling_right.set_buffer(buffer)
|
|
||||||
view.sibling_right.get_parent().show()
|
|
||||||
view.sibling_right.grab_focus()
|
|
||||||
|
|
||||||
if next_file:
|
|
||||||
view.set_buffer(next_file.buffer)
|
|
||||||
else:
|
|
||||||
view.command.exec("new_file")
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
|
||||||
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from plugins.plugin_types import PluginCode
|
|
||||||
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from . import create_split_view, \
|
|
||||||
close_split_view, \
|
|
||||||
focus_left_sibling, \
|
|
||||||
focus_right_sibling, \
|
|
||||||
move_to_left_sibling, \
|
|
||||||
move_to_right_sibling
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginCode):
|
|
||||||
def __init__(self):
|
|
||||||
super(Plugin, self).__init__()
|
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
gemit_to = self.emit_to
|
|
||||||
self._manage_signals("register_command")
|
|
||||||
|
|
||||||
def unload(self):
|
|
||||||
self._manage_signals("unregister_command")
|
|
||||||
|
|
||||||
def _manage_signals(self, action: str):
|
|
||||||
_create_split_view = create_split_view
|
|
||||||
_close_split_view = close_split_view
|
|
||||||
_create_split_view.emit_to = self.emit_to
|
|
||||||
_close_split_view.emit_to = self.emit_to
|
|
||||||
|
|
||||||
event = Event_Factory.create_event(action,
|
|
||||||
command_name = "create_split_view",
|
|
||||||
command = _create_split_view,
|
|
||||||
binding_mode = "released",
|
|
||||||
binding = ["<Control>\\", "<Shift><Control>|"]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.emit_to("source_views", event)
|
|
||||||
|
|
||||||
event = Event_Factory.create_event(action,
|
|
||||||
command_name = "close_split_view",
|
|
||||||
command = _close_split_view,
|
|
||||||
binding_mode = "released",
|
|
||||||
binding = "<Shift><Control>w"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.emit_to("source_views", event)
|
|
||||||
|
|
||||||
event = Event_Factory.create_event(action,
|
|
||||||
command_name = "focus_left_sibling",
|
|
||||||
command = focus_left_sibling,
|
|
||||||
binding_mode = "released",
|
|
||||||
binding = "<Control>Page_Up"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.emit_to("source_views", event)
|
|
||||||
|
|
||||||
event = Event_Factory.create_event(action,
|
|
||||||
command_name = "focus_right_sibling",
|
|
||||||
command = focus_right_sibling,
|
|
||||||
binding_mode = "released",
|
|
||||||
binding = "<Control>Page_Down"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.emit_to("source_views", event)
|
|
||||||
|
|
||||||
event = Event_Factory.create_event(action,
|
|
||||||
command_name = "move_to_left_sibling",
|
|
||||||
command = move_to_left_sibling,
|
|
||||||
binding_mode = "released",
|
|
||||||
binding = "<Control><Shift>Up"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.emit_to("source_views", event)
|
|
||||||
|
|
||||||
event = Event_Factory.create_event(action,
|
|
||||||
command_name = "move_to_right_sibling",
|
|
||||||
command = move_to_right_sibling,
|
|
||||||
binding_mode = "released",
|
|
||||||
binding = "<Control><Shift>Down"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.emit_to("source_views", event)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
...
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Module
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Package
|
|
||||||
"""
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Example Completer",
|
|
||||||
"author": "John Doe",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"support": "",
|
|
||||||
"requests": {}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
|
||||||
|
|
||||||
from .provider import Provider
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginCode):
|
|
||||||
def __init__(self):
|
|
||||||
super(Plugin, self).__init__()
|
|
||||||
|
|
||||||
self.provider: Provider = None
|
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
self.provider = Provider()
|
|
||||||
|
|
||||||
event = Event_Factory.create_event(
|
|
||||||
"register_provider",
|
|
||||||
provider_name = "Example Completer",
|
|
||||||
provider = self.provider,
|
|
||||||
language_ids = []
|
|
||||||
)
|
|
||||||
self.emit_to("completion", event)
|
|
||||||
|
|
||||||
def unload(self):
|
|
||||||
event = Event_Factory.create_event(
|
|
||||||
"unregister_provider",
|
|
||||||
provider_name = "Example Completer"
|
|
||||||
)
|
|
||||||
self.emit_to("completion", event)
|
|
||||||
|
|
||||||
self.provider = None
|
|
||||||
del self.provider
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
...
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GObject
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from .provider_response_cache import ProviderResponseCache
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Provider(GObject.GObject, GtkSource.CompletionProvider):
|
|
||||||
"""
|
|
||||||
This is a custom Completion Example Provider.
|
|
||||||
# NOTE: used information from here --> https://warroom.rsmus.com/do-that-auto-complete/
|
|
||||||
"""
|
|
||||||
__gtype_name__ = 'ExampleCompletionProvider'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(Provider, self).__init__()
|
|
||||||
|
|
||||||
self.response_cache: ProviderResponseCache = ProviderResponseCache()
|
|
||||||
|
|
||||||
|
|
||||||
def do_get_name(self):
|
|
||||||
""" Returns: a new string containing the name of the provider. """
|
|
||||||
return 'Example Code Completion'
|
|
||||||
|
|
||||||
def do_match(self, context):
|
|
||||||
# Note: If provider is in interactive activation then need to check
|
|
||||||
# view focus as otherwise non focus views start trying to grab it.
|
|
||||||
completion = context.get_property("completion")
|
|
||||||
if not completion.get_view().has_focus(): return
|
|
||||||
|
|
||||||
word = self.response_cache.get_word(context)
|
|
||||||
if not word or len(word) < 2: return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def do_get_priority(self):
|
|
||||||
""" Determin position in result list along other providor results. """
|
|
||||||
return 5
|
|
||||||
|
|
||||||
def do_activate_proposal(self, proposal, iter_):
|
|
||||||
""" Manually handle actual completion insert or set flags and handle normally. """
|
|
||||||
|
|
||||||
buffer = iter_.get_buffer()
|
|
||||||
# Note: Flag mostly intended for SourceViewsMultiInsertState
|
|
||||||
# to insure marker processes inserted text correctly.
|
|
||||||
buffer.is_processing_completion = True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def do_get_activation(self):
|
|
||||||
""" The context for when a provider will show results """
|
|
||||||
# return GtkSource.CompletionActivation.NONE
|
|
||||||
# return GtkSource.CompletionActivation.USER_REQUESTED
|
|
||||||
# return GtkSource.CompletionActivation.USER_REQUESTED | GtkSource.CompletionActivation.INTERACTIVE
|
|
||||||
return GtkSource.CompletionActivation.INTERACTIVE
|
|
||||||
|
|
||||||
def do_populate(self, context):
|
|
||||||
results = self.response_cache.filter_with_context(context)
|
|
||||||
proposals = []
|
|
||||||
|
|
||||||
for entry in results:
|
|
||||||
proposals.append(
|
|
||||||
self.response_cache.create_completion_item(
|
|
||||||
entry["label"],
|
|
||||||
entry["text"],
|
|
||||||
entry["info"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
context.add_proposals(self, proposals, True)
|
|
||||||
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
import re
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import GLib
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Code_Event_Types
|
|
||||||
|
|
||||||
from core.widgets.code.completion_providers.provider_response_cache_base import ProviderResponseCacheBase
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderResponseCache(ProviderResponseCacheBase):
|
|
||||||
def __init__(self):
|
|
||||||
super(ProviderResponseCache, self).__init__()
|
|
||||||
|
|
||||||
# Note: Using asyncio.run causes a keyboard trap that prevents app
|
|
||||||
# closure from terminal. ThreadPoolExecutor seems to not have such issues...
|
|
||||||
self.executor = ThreadPoolExecutor(max_workers = 1)
|
|
||||||
self.matchers: dict = {
|
|
||||||
"hello": {
|
|
||||||
"label": "Hello, World!",
|
|
||||||
"text": "Hello, World!",
|
|
||||||
"info": GLib.markup_escape_text( "<b>Says the first ever program developers write...</b>" )
|
|
||||||
},
|
|
||||||
"foo": {
|
|
||||||
"label": "foo",
|
|
||||||
"text": "foo }}"
|
|
||||||
},
|
|
||||||
"bar": {
|
|
||||||
"label": "bar",
|
|
||||||
"text": "bar }}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def filter(self, word: str) -> list[dict]:
|
|
||||||
...
|
|
||||||
|
|
||||||
def filter_with_context(self, context) -> list[dict]:
|
|
||||||
"""
|
|
||||||
In this instance, it will do 2 things:
|
|
||||||
1) always provide Hello World! (Not ideal but an option so its in the example)
|
|
||||||
2) Utilizes the Gtk.TextIter from the TextBuffer to determine if there is a jinja
|
|
||||||
example of '{{ custom.' if so it will provide you with the options of foo and bar.
|
|
||||||
If selected it will insert foo }} or bar }}, completing your syntax...
|
|
||||||
|
|
||||||
PLEASE NOTE the GtkTextIter Logic and regex are really rough and should be adjusted and tuned
|
|
||||||
"""
|
|
||||||
|
|
||||||
proposals: list[dict] = [
|
|
||||||
{
|
|
||||||
"label": self.matchers[ "hello" ]["label"],
|
|
||||||
"text": self.matchers[ "hello" ]["text"],
|
|
||||||
"info": self.matchers[ "hello" ]["info"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
# Gtk Versions differ on get_iter responses...
|
|
||||||
end_iter = context.get_iter()
|
|
||||||
if not isinstance(end_iter, Gtk.TextIter):
|
|
||||||
_, end_iter = context.get_iter()
|
|
||||||
|
|
||||||
if not end_iter: return
|
|
||||||
|
|
||||||
buf = end_iter.get_buffer()
|
|
||||||
mov_iter = end_iter.copy()
|
|
||||||
if mov_iter.backward_search('{{', Gtk.TextSearchFlags.VISIBLE_ONLY):
|
|
||||||
mov_iter, _ = mov_iter.backward_search('{{', Gtk.TextSearchFlags.VISIBLE_ONLY)
|
|
||||||
left_text = buf.get_text(mov_iter, end_iter, True)
|
|
||||||
else:
|
|
||||||
left_text = ''
|
|
||||||
|
|
||||||
if re.match(r'.*\{\{\s*custom\.$', left_text):
|
|
||||||
# optionally proposed based on left search via regex
|
|
||||||
proposals.append(
|
|
||||||
{
|
|
||||||
"label": self.matchers[ "foo" ]["label"],
|
|
||||||
"text": self.matchers[ "foo" ]["text"],
|
|
||||||
"info": ""
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# optionally proposed based on left search via regex
|
|
||||||
proposals.append(
|
|
||||||
{
|
|
||||||
"label": self.matchers[ "bar" ]["label"],
|
|
||||||
"text": self.matchers[ "bar" ]["text"],
|
|
||||||
"info": ""
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return proposals
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Module
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Package
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from .parser import load, loads
|
|
||||||
from .writer import dump, dumps
|
|
||||||
from .speg import ParseError
|
|
||||||
@@ -1,295 +0,0 @@
|
|||||||
from .speg import peg
|
|
||||||
import re, sys
|
|
||||||
|
|
||||||
if sys.version_info[0] == 2:
|
|
||||||
_chr = unichr
|
|
||||||
else:
|
|
||||||
_chr = chr
|
|
||||||
|
|
||||||
def load(fin):
|
|
||||||
return loads(fin.read())
|
|
||||||
|
|
||||||
def loads(s):
|
|
||||||
if isinstance(s, bytes):
|
|
||||||
s = s.decode('utf-8')
|
|
||||||
if s.startswith(u'\ufeff'):
|
|
||||||
s = s[1:]
|
|
||||||
return peg(s.replace('\r\n', '\n'), _p_root)
|
|
||||||
|
|
||||||
def _p_ws(p):
|
|
||||||
p('[ \t]*')
|
|
||||||
|
|
||||||
def _p_nl(p):
|
|
||||||
p(r'([ \t]*(?:#[^\n]*)?\r?\n)+')
|
|
||||||
|
|
||||||
def _p_ews(p):
|
|
||||||
with p:
|
|
||||||
p(_p_nl)
|
|
||||||
p(_p_ws)
|
|
||||||
|
|
||||||
def _p_id(p):
|
|
||||||
return p(r'[$a-zA-Z_][$0-9a-zA-Z_]*')
|
|
||||||
|
|
||||||
_escape_table = {
|
|
||||||
'r': '\r',
|
|
||||||
'n': '\n',
|
|
||||||
't': '\t',
|
|
||||||
'f': '\f',
|
|
||||||
'b': '\b',
|
|
||||||
}
|
|
||||||
def _p_unescape(p):
|
|
||||||
esc = p('\\\\(?:u[0-9a-fA-F]{4}|[^\n])')
|
|
||||||
if esc[1] == 'u':
|
|
||||||
return _chr(int(esc[2:], 16))
|
|
||||||
return _escape_table.get(esc[1:], esc[1:])
|
|
||||||
|
|
||||||
_re_indent = re.compile(r'[ \t]*')
|
|
||||||
def _p_block_str(p, c):
|
|
||||||
p(r'{c}{c}{c}'.format(c=c))
|
|
||||||
lines = [['']]
|
|
||||||
with p:
|
|
||||||
while True:
|
|
||||||
s = p(r'(?:{c}(?!{c}{c})|[^{c}\\])*'.format(c=c))
|
|
||||||
l = s.split('\n')
|
|
||||||
lines[-1].append(l[0])
|
|
||||||
lines.extend([x] for x in l[1:])
|
|
||||||
if p(r'(?:\\\n[ \t]*)*'):
|
|
||||||
continue
|
|
||||||
p.commit()
|
|
||||||
lines[-1].append(p(_p_unescape))
|
|
||||||
p(r'{c}{c}{c}'.format(c=c))
|
|
||||||
|
|
||||||
lines = [''.join(l) for l in lines]
|
|
||||||
strip_ws = len(lines) > 1
|
|
||||||
if strip_ws and all(c in ' \t' for c in lines[-1]):
|
|
||||||
lines.pop()
|
|
||||||
|
|
||||||
indent = None
|
|
||||||
for line in lines[1:]:
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
if indent is None:
|
|
||||||
indent = _re_indent.match(line).group(0)
|
|
||||||
continue
|
|
||||||
for i, (c1, c2) in enumerate(zip(indent, line)):
|
|
||||||
if c1 != c2:
|
|
||||||
indent = indent[:i]
|
|
||||||
break
|
|
||||||
|
|
||||||
ind_len = len(indent or '')
|
|
||||||
if strip_ws and all(c in ' \t' for c in lines[0]):
|
|
||||||
lines = [line[ind_len:] for line in lines[1:]]
|
|
||||||
else:
|
|
||||||
lines[1:] = [line[ind_len:] for line in lines[1:]]
|
|
||||||
|
|
||||||
return '\n'.join(lines)
|
|
||||||
|
|
||||||
_re_mstr_nl = re.compile(r'(?:[ \t]*\n)+[ \t]*')
|
|
||||||
_re_mstr_trailing_nl = re.compile(_re_mstr_nl.pattern + r'\Z')
|
|
||||||
def _p_multiline_str(p, c):
|
|
||||||
p('{c}(?!{c}{c})(?:[ \t]*\n[ \t]*)?'.format(c=c))
|
|
||||||
string_parts = []
|
|
||||||
with p:
|
|
||||||
while True:
|
|
||||||
string_parts.append(p(r'[^{c}\\]*'.format(c=c)))
|
|
||||||
if p(r'(?:\\\n[ \t]*)*'):
|
|
||||||
string_parts.append('')
|
|
||||||
continue
|
|
||||||
p.commit()
|
|
||||||
string_parts.append(p(_p_unescape))
|
|
||||||
p(c)
|
|
||||||
string_parts[-1] = _re_mstr_trailing_nl.sub('', string_parts[-1])
|
|
||||||
string_parts[::2] = [_re_mstr_nl.sub(' ', part) for part in string_parts[::2]]
|
|
||||||
return ''.join(string_parts)
|
|
||||||
|
|
||||||
def _p_string(p):
|
|
||||||
with p:
|
|
||||||
return p(_p_block_str, '"')
|
|
||||||
with p:
|
|
||||||
return p(_p_block_str, "'")
|
|
||||||
with p:
|
|
||||||
return p(_p_multiline_str, '"')
|
|
||||||
return p(_p_multiline_str, "'")
|
|
||||||
|
|
||||||
def _p_array_value(p):
|
|
||||||
with p:
|
|
||||||
p(_p_nl)
|
|
||||||
return p(_p_object)
|
|
||||||
with p:
|
|
||||||
p(_p_ws)
|
|
||||||
return p(_p_line_object)
|
|
||||||
p(_p_ews)
|
|
||||||
return p(_p_simple_value)
|
|
||||||
|
|
||||||
def _p_key(p):
|
|
||||||
with p:
|
|
||||||
return p(_p_id)
|
|
||||||
return p(_p_string)
|
|
||||||
|
|
||||||
def _p_flow_kv(p):
|
|
||||||
k = p(_p_key)
|
|
||||||
p(_p_ews)
|
|
||||||
p(':')
|
|
||||||
with p:
|
|
||||||
p(_p_nl)
|
|
||||||
return k, p(_p_object)
|
|
||||||
with p:
|
|
||||||
p(_p_ws)
|
|
||||||
return k, p(_p_line_object)
|
|
||||||
p(_p_ews)
|
|
||||||
return k, p(_p_simple_value)
|
|
||||||
|
|
||||||
def _p_flow_obj_sep(p):
|
|
||||||
with p:
|
|
||||||
p(_p_ews)
|
|
||||||
p(',')
|
|
||||||
p(_p_ews)
|
|
||||||
return
|
|
||||||
|
|
||||||
p(_p_nl)
|
|
||||||
p(_p_ws)
|
|
||||||
|
|
||||||
def _p_simple_value(p):
|
|
||||||
with p:
|
|
||||||
p('null')
|
|
||||||
return None
|
|
||||||
|
|
||||||
with p:
|
|
||||||
p('false')
|
|
||||||
return False
|
|
||||||
with p:
|
|
||||||
p('true')
|
|
||||||
return True
|
|
||||||
|
|
||||||
with p:
|
|
||||||
return int(p('0b[01]+')[2:], 2)
|
|
||||||
with p:
|
|
||||||
return int(p('0o[0-7]+')[2:], 8)
|
|
||||||
with p:
|
|
||||||
return int(p('0x[0-9a-fA-F]+')[2:], 16)
|
|
||||||
with p:
|
|
||||||
return float(p(r'-?(?:[1-9][0-9]*|0)?\.[0-9]+(?:[Ee][\+-]?[0-9]+)?|(?:[1-9][0-9]*|0)(?:\.[0-9]+)?[Ee][\+-]?[0-9]+'))
|
|
||||||
with p:
|
|
||||||
return int(p('-?[1-9][0-9]*|0'), 10)
|
|
||||||
|
|
||||||
with p:
|
|
||||||
return p(_p_string)
|
|
||||||
|
|
||||||
with p:
|
|
||||||
p(r'\[')
|
|
||||||
r = []
|
|
||||||
with p:
|
|
||||||
p.set('I', '')
|
|
||||||
r.append(p(_p_array_value))
|
|
||||||
with p:
|
|
||||||
while True:
|
|
||||||
with p:
|
|
||||||
p(_p_ews)
|
|
||||||
p(',')
|
|
||||||
rr = p(_p_array_value)
|
|
||||||
if not p:
|
|
||||||
p(_p_nl)
|
|
||||||
with p:
|
|
||||||
rr = p(_p_object)
|
|
||||||
if not p:
|
|
||||||
p(_p_ews)
|
|
||||||
rr = p(_p_simple_value)
|
|
||||||
r.append(rr)
|
|
||||||
p.commit()
|
|
||||||
with p:
|
|
||||||
p(_p_ews)
|
|
||||||
p(',')
|
|
||||||
p(_p_ews)
|
|
||||||
p(r'\]')
|
|
||||||
return r
|
|
||||||
|
|
||||||
p(r'\{')
|
|
||||||
|
|
||||||
r = {}
|
|
||||||
p(_p_ews)
|
|
||||||
with p:
|
|
||||||
p.set('I', '')
|
|
||||||
k, v = p(_p_flow_kv)
|
|
||||||
r[k] = v
|
|
||||||
with p:
|
|
||||||
while True:
|
|
||||||
p(_p_flow_obj_sep)
|
|
||||||
k, v = p(_p_flow_kv)
|
|
||||||
r[k] = v
|
|
||||||
p.commit()
|
|
||||||
p(_p_ews)
|
|
||||||
with p:
|
|
||||||
p(',')
|
|
||||||
p(_p_ews)
|
|
||||||
p(r'\}')
|
|
||||||
return r
|
|
||||||
|
|
||||||
def _p_line_kv(p):
|
|
||||||
k = p(_p_key)
|
|
||||||
p(_p_ws)
|
|
||||||
p(':')
|
|
||||||
p(_p_ws)
|
|
||||||
with p:
|
|
||||||
p(_p_nl)
|
|
||||||
p(p.get('I'))
|
|
||||||
return k, p(_p_indented_object)
|
|
||||||
with p:
|
|
||||||
return k, p(_p_line_object)
|
|
||||||
with p:
|
|
||||||
return k, p(_p_simple_value)
|
|
||||||
p(_p_nl)
|
|
||||||
p(p.get('I'))
|
|
||||||
p('[ \t]')
|
|
||||||
p(_p_ws)
|
|
||||||
return k, p(_p_simple_value)
|
|
||||||
|
|
||||||
def _p_line_object(p):
|
|
||||||
k, v = p(_p_line_kv)
|
|
||||||
r = { k: v }
|
|
||||||
with p:
|
|
||||||
while True:
|
|
||||||
p(_p_ws)
|
|
||||||
p(',')
|
|
||||||
p(_p_ws)
|
|
||||||
k, v = p(_p_line_kv)
|
|
||||||
r[k] = v # uniqueness
|
|
||||||
p.commit()
|
|
||||||
return r
|
|
||||||
|
|
||||||
def _p_object(p):
|
|
||||||
p.set('I', p.get('I') + p('[ \t]*'))
|
|
||||||
r = p(_p_line_object)
|
|
||||||
with p:
|
|
||||||
while True:
|
|
||||||
p(_p_ws)
|
|
||||||
with p:
|
|
||||||
p(',')
|
|
||||||
p(_p_nl)
|
|
||||||
p(p.get('I'))
|
|
||||||
rr = p(_p_line_object)
|
|
||||||
r.update(rr) # unqueness
|
|
||||||
p.commit()
|
|
||||||
return r
|
|
||||||
|
|
||||||
def _p_indented_object(p):
|
|
||||||
p.set('I', p.get('I') + p('[ \t]'))
|
|
||||||
return p(_p_object)
|
|
||||||
|
|
||||||
def _p_root(p):
|
|
||||||
with p:
|
|
||||||
p(_p_nl)
|
|
||||||
|
|
||||||
with p:
|
|
||||||
p.set('I', '')
|
|
||||||
r = p(_p_object)
|
|
||||||
p(_p_ws)
|
|
||||||
with p:
|
|
||||||
p(',')
|
|
||||||
|
|
||||||
if not p:
|
|
||||||
p(_p_ws)
|
|
||||||
r = p(_p_simple_value)
|
|
||||||
|
|
||||||
p(_p_ews)
|
|
||||||
p(p.eof)
|
|
||||||
return r
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
from .peg import peg, ParseError
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
import sys, re
|
|
||||||
|
|
||||||
class ParseError(Exception):
|
|
||||||
def __init__(self, msg, text, offset, line, col):
|
|
||||||
self.msg = msg
|
|
||||||
self.text = text
|
|
||||||
self.offset = offset
|
|
||||||
self.line = line
|
|
||||||
self.col = col
|
|
||||||
super(ParseError, self).__init__(msg, offset, line, col)
|
|
||||||
|
|
||||||
if sys.version_info[0] == 2:
|
|
||||||
_basestr = basestring
|
|
||||||
else:
|
|
||||||
_basestr = str
|
|
||||||
|
|
||||||
def peg(s, r):
|
|
||||||
p = _Peg(s)
|
|
||||||
try:
|
|
||||||
return p(r)
|
|
||||||
except _UnexpectedError as e:
|
|
||||||
offset = max(p._errors)
|
|
||||||
err = p._errors[offset]
|
|
||||||
raise ParseError(err.msg, s, offset, err.line, err.col)
|
|
||||||
|
|
||||||
class _UnexpectedError(RuntimeError):
|
|
||||||
def __init__(self, state, expr):
|
|
||||||
self.state = state
|
|
||||||
self.expr = expr
|
|
||||||
|
|
||||||
class _PegState:
|
|
||||||
def __init__(self, pos, line, col):
|
|
||||||
self.pos = pos
|
|
||||||
self.line = line
|
|
||||||
self.col = col
|
|
||||||
self.vars = {}
|
|
||||||
self.commited = False
|
|
||||||
|
|
||||||
class _PegError:
|
|
||||||
def __init__(self, msg, line, col):
|
|
||||||
self.msg = msg
|
|
||||||
self.line = line
|
|
||||||
self.col = col
|
|
||||||
|
|
||||||
class _Peg:
|
|
||||||
def __init__(self, s):
|
|
||||||
self._s = s
|
|
||||||
self._states = [_PegState(0, 1, 1)]
|
|
||||||
self._errors = {}
|
|
||||||
self._re_cache = {}
|
|
||||||
|
|
||||||
def __call__(self, r, *args, **kw):
|
|
||||||
if isinstance(r, _basestr):
|
|
||||||
compiled = self._re_cache.get(r)
|
|
||||||
if not compiled:
|
|
||||||
compiled = re.compile(r)
|
|
||||||
self._re_cache[r] = compiled
|
|
||||||
st = self._states[-1]
|
|
||||||
m = compiled.match(self._s[st.pos:])
|
|
||||||
if not m:
|
|
||||||
self.error(expr=r, err=kw.get('err'))
|
|
||||||
|
|
||||||
ms = m.group(0)
|
|
||||||
st.pos += len(ms)
|
|
||||||
nl_pos = ms.rfind('\n')
|
|
||||||
if nl_pos < 0:
|
|
||||||
st.col += len(ms)
|
|
||||||
else:
|
|
||||||
st.col = len(ms) - nl_pos
|
|
||||||
st.line += ms[:nl_pos].count('\n') + 1
|
|
||||||
return ms
|
|
||||||
else:
|
|
||||||
kw.pop('err', None)
|
|
||||||
return r(self, *args, **kw)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
pos = self._states[-1].pos
|
|
||||||
vars = {}
|
|
||||||
for st in self._states:
|
|
||||||
vars.update(st.vars)
|
|
||||||
return '_Peg(%r, %r)' % (self._s[:pos] + '*' + self._s[pos:], vars)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def eof(p):
|
|
||||||
if p._states[-1].pos != len(p._s):
|
|
||||||
p.error()
|
|
||||||
|
|
||||||
def error(self, err=None, expr=None):
|
|
||||||
st = self._states[-1]
|
|
||||||
if err is None:
|
|
||||||
err = 'expected {!r}, found {!r}'.format(expr, self._s[st.pos:st.pos+4])
|
|
||||||
self._errors[st.pos] = _PegError(err, st.line, st.col)
|
|
||||||
raise _UnexpectedError(st, expr)
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
|
||||||
for state in self._states[::-1]:
|
|
||||||
if key in state.vars:
|
|
||||||
return state.vars[key][0]
|
|
||||||
return default
|
|
||||||
|
|
||||||
def set(self, key, value):
|
|
||||||
self._states[-1].vars[key] = value, False
|
|
||||||
|
|
||||||
def set_global(self, key, value):
|
|
||||||
self._states[-1].vars[key] = value, True
|
|
||||||
|
|
||||||
def opt(self, *args, **kw):
|
|
||||||
with self:
|
|
||||||
return self(*args, **kw)
|
|
||||||
|
|
||||||
def not_(self, s, *args, **kw):
|
|
||||||
with self:
|
|
||||||
self(s)
|
|
||||||
self.error()
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self._states[-1].committed = False
|
|
||||||
self._states.append(_PegState(self._states[-1].pos, self._states[-1].line, self._states[-1].col))
|
|
||||||
|
|
||||||
def __exit__(self, type, value, traceback):
|
|
||||||
if type is None:
|
|
||||||
self.commit()
|
|
||||||
self._states.pop()
|
|
||||||
return type == _UnexpectedError
|
|
||||||
|
|
||||||
def commit(self):
|
|
||||||
cur = self._states[-1]
|
|
||||||
prev = self._states[-2]
|
|
||||||
|
|
||||||
for key in cur.vars:
|
|
||||||
val, g = cur.vars[key]
|
|
||||||
if not g:
|
|
||||||
continue
|
|
||||||
if key in prev.vars:
|
|
||||||
prev.vars[key] = val, prev.vars[key][1]
|
|
||||||
else:
|
|
||||||
prev.vars[key] = val, True
|
|
||||||
|
|
||||||
prev.pos = cur.pos
|
|
||||||
prev.line = cur.line
|
|
||||||
prev.col = cur.col
|
|
||||||
prev.committed = True
|
|
||||||
|
|
||||||
def __nonzero__(self):
|
|
||||||
return self._states[-1].committed
|
|
||||||
|
|
||||||
__bool__ = __nonzero__
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
import re, json, sys
|
|
||||||
|
|
||||||
if sys.version_info[0] == 2:
|
|
||||||
def _is_num(o):
|
|
||||||
return isinstance(o, int) or isinstance(o, long) or isinstance(o, float)
|
|
||||||
def _stringify(o):
|
|
||||||
if isinstance(o, str):
|
|
||||||
return unicode(o)
|
|
||||||
if isinstance(o, unicode):
|
|
||||||
return o
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
def _is_num(o):
|
|
||||||
return isinstance(o, int) or isinstance(o, float)
|
|
||||||
def _stringify(o):
|
|
||||||
if isinstance(o, bytes):
|
|
||||||
return o.decode()
|
|
||||||
if isinstance(o, str):
|
|
||||||
return o
|
|
||||||
return None
|
|
||||||
|
|
||||||
_id_re = re.compile(r'[$a-zA-Z_][$0-9a-zA-Z_]*\Z')
|
|
||||||
|
|
||||||
class CSONEncoder:
|
|
||||||
def __init__(self, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False,
|
|
||||||
indent=None, default=None):
|
|
||||||
self._skipkeys = skipkeys
|
|
||||||
self._ensure_ascii = ensure_ascii
|
|
||||||
self._allow_nan = allow_nan
|
|
||||||
self._sort_keys = sort_keys
|
|
||||||
self._indent = ' ' * (indent or 4)
|
|
||||||
self._default = default
|
|
||||||
if check_circular:
|
|
||||||
self._obj_stack = set()
|
|
||||||
else:
|
|
||||||
self._obj_stack = None
|
|
||||||
|
|
||||||
def _format_simple_val(self, o):
|
|
||||||
if o is None:
|
|
||||||
return 'null'
|
|
||||||
if isinstance(o, bool):
|
|
||||||
return 'true' if o else 'false'
|
|
||||||
if _is_num(o):
|
|
||||||
return str(o)
|
|
||||||
s = _stringify(o)
|
|
||||||
if s is not None:
|
|
||||||
return self._escape_string(s)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _escape_string(self, s):
|
|
||||||
r = json.dumps(s, ensure_ascii=self._ensure_ascii)
|
|
||||||
return u"'{}'".format(r[1:-1].replace("'", r"\'"))
|
|
||||||
|
|
||||||
def _escape_key(self, s):
|
|
||||||
if s is None or isinstance(s, bool) or _is_num(s):
|
|
||||||
s = str(s)
|
|
||||||
s = _stringify(s)
|
|
||||||
if s is None:
|
|
||||||
if self._skipkeys:
|
|
||||||
return None
|
|
||||||
raise TypeError('keys must be a string')
|
|
||||||
if not _id_re.match(s):
|
|
||||||
return self._escape_string(s)
|
|
||||||
return s
|
|
||||||
|
|
||||||
def _push_obj(self, o):
|
|
||||||
if self._obj_stack is not None:
|
|
||||||
if id(o) in self._obj_stack:
|
|
||||||
raise ValueError('Circular reference detected')
|
|
||||||
self._obj_stack.add(id(o))
|
|
||||||
|
|
||||||
def _pop_obj(self, o):
|
|
||||||
if self._obj_stack is not None:
|
|
||||||
self._obj_stack.remove(id(o))
|
|
||||||
|
|
||||||
def _encode(self, o, obj_val=False, indent='', force_flow=False):
|
|
||||||
if isinstance(o, list):
|
|
||||||
if not o:
|
|
||||||
if obj_val:
|
|
||||||
yield ' []\n'
|
|
||||||
else:
|
|
||||||
yield indent
|
|
||||||
yield '[]\n'
|
|
||||||
else:
|
|
||||||
if obj_val:
|
|
||||||
yield ' [\n'
|
|
||||||
else:
|
|
||||||
yield indent
|
|
||||||
yield '[\n'
|
|
||||||
indent = indent + self._indent
|
|
||||||
self._push_obj(o)
|
|
||||||
for v in o:
|
|
||||||
for chunk in self._encode(v, obj_val=False, indent=indent, force_flow=True):
|
|
||||||
yield chunk
|
|
||||||
self._pop_obj(o)
|
|
||||||
yield indent[:-len(self._indent)]
|
|
||||||
yield ']\n'
|
|
||||||
elif isinstance(o, dict):
|
|
||||||
items = [(self._escape_key(k), v) for k, v in o.items()]
|
|
||||||
if self._skipkeys:
|
|
||||||
items = [(k, v) for k, v in items if k is not None]
|
|
||||||
if self._sort_keys:
|
|
||||||
items.sort()
|
|
||||||
if force_flow or not items:
|
|
||||||
if not items:
|
|
||||||
if obj_val:
|
|
||||||
yield ' {}\n'
|
|
||||||
else:
|
|
||||||
yield indent
|
|
||||||
yield '{}\n'
|
|
||||||
else:
|
|
||||||
if obj_val:
|
|
||||||
yield ' {\n'
|
|
||||||
else:
|
|
||||||
yield indent
|
|
||||||
yield '{\n'
|
|
||||||
indent = indent + self._indent
|
|
||||||
self._push_obj(o)
|
|
||||||
for k, v in items:
|
|
||||||
yield indent
|
|
||||||
yield k
|
|
||||||
yield ':'
|
|
||||||
for chunk in self._encode(v, obj_val=True, indent=indent + self._indent, force_flow=False):
|
|
||||||
yield chunk
|
|
||||||
self._pop_obj(o)
|
|
||||||
yield indent[:-len(self._indent)]
|
|
||||||
yield '}\n'
|
|
||||||
else:
|
|
||||||
if obj_val:
|
|
||||||
yield '\n'
|
|
||||||
self._push_obj(o)
|
|
||||||
for k, v in items:
|
|
||||||
yield indent
|
|
||||||
yield k
|
|
||||||
yield ':'
|
|
||||||
for chunk in self._encode(v, obj_val=True, indent=indent + self._indent, force_flow=False):
|
|
||||||
yield chunk
|
|
||||||
self._pop_obj(o)
|
|
||||||
else:
|
|
||||||
v = self._format_simple_val(o)
|
|
||||||
if v is None:
|
|
||||||
self._push_obj(o)
|
|
||||||
v = self.default(o)
|
|
||||||
for chunk in self._encode(v, obj_val=obj_val, indent=indent, force_flow=force_flow):
|
|
||||||
yield chunk
|
|
||||||
self._pop_obj(o)
|
|
||||||
else:
|
|
||||||
if obj_val:
|
|
||||||
yield ' '
|
|
||||||
else:
|
|
||||||
yield indent
|
|
||||||
yield v
|
|
||||||
yield '\n'
|
|
||||||
|
|
||||||
def iterencode(self, o):
|
|
||||||
return self._encode(o)
|
|
||||||
|
|
||||||
def encode(self, o):
|
|
||||||
return ''.join(self.iterencode(o))
|
|
||||||
|
|
||||||
def default(self, o):
|
|
||||||
if self._default is None:
|
|
||||||
raise TypeError('Cannot serialize an object of type {}'.format(type(o).__name__))
|
|
||||||
return self._default(o)
|
|
||||||
|
|
||||||
def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None,
|
|
||||||
indent=None, default=None, sort_keys=False, **kw):
|
|
||||||
if indent is None and cls is None:
|
|
||||||
return json.dump(obj, fp, skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular,
|
|
||||||
allow_nan=allow_nan, default=default, sort_keys=sort_keys, separators=(',', ':'))
|
|
||||||
|
|
||||||
if cls is None:
|
|
||||||
cls = CSONEncoder
|
|
||||||
encoder = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular,
|
|
||||||
allow_nan=allow_nan, sort_keys=sort_keys, indent=indent, default=default, **kw)
|
|
||||||
|
|
||||||
for chunk in encoder.iterencode(obj):
|
|
||||||
fp.write(chunk)
|
|
||||||
|
|
||||||
def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None,
|
|
||||||
default=None, sort_keys=False, **kw):
|
|
||||||
if indent is None and cls is None:
|
|
||||||
return json.dumps(obj, skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular,
|
|
||||||
allow_nan=allow_nan, default=default, sort_keys=sort_keys, separators=(',', ':'))
|
|
||||||
|
|
||||||
if cls is None:
|
|
||||||
cls = CSONEncoder
|
|
||||||
encoder = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular,
|
|
||||||
allow_nan=allow_nan, sort_keys=sort_keys, indent=indent, default=default, **kw)
|
|
||||||
|
|
||||||
return encoder.encode(obj)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Snippets Completer",
|
|
||||||
"author": "ITDominator",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"support": "",
|
|
||||||
"requests": {}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
|
||||||
|
|
||||||
from .provider import Provider
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginCode):
|
|
||||||
def __init__(self):
|
|
||||||
super(Plugin, self).__init__()
|
|
||||||
|
|
||||||
self.provider: Provider = None
|
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
self.provider = Provider()
|
|
||||||
|
|
||||||
event = Event_Factory.create_event(
|
|
||||||
"register_provider",
|
|
||||||
provider_name = "Snippets Completer",
|
|
||||||
provider = self.provider,
|
|
||||||
language_ids = []
|
|
||||||
)
|
|
||||||
self.emit_to("completion", event)
|
|
||||||
|
|
||||||
def unload(self):
|
|
||||||
event = Event_Factory.create_event(
|
|
||||||
"unregister_provider",
|
|
||||||
provider_name = "Snippets Completer"
|
|
||||||
)
|
|
||||||
self.emit_to("completion", event)
|
|
||||||
|
|
||||||
self.provider = None
|
|
||||||
del self.provider
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
...
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import re
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GObject
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from .provider_response_cache import ProviderResponseCache
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Provider(GObject.GObject, GtkSource.CompletionProvider):
|
|
||||||
"""
|
|
||||||
This is a Snippits Completion Provider.
|
|
||||||
# NOTE: used information from here --> https://warroom.rsmus.com/do-that-auto-complete/
|
|
||||||
"""
|
|
||||||
__gtype_name__ = 'SnippetsCompletionProvider'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(Provider, self).__init__()
|
|
||||||
|
|
||||||
self.response_cache: ProviderResponseCache = ProviderResponseCache()
|
|
||||||
|
|
||||||
|
|
||||||
def do_get_name(self):
|
|
||||||
return 'Snippits Completion'
|
|
||||||
|
|
||||||
def do_match(self, context):
|
|
||||||
word = self.response_cache.get_word(context)
|
|
||||||
if not word or len(word) < 2: return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def do_get_priority(self):
|
|
||||||
return 2
|
|
||||||
|
|
||||||
def do_activate_proposal(self, proposal, iter_):
|
|
||||||
buffer = iter_.get_buffer()
|
|
||||||
# Note: Flag mostly intended for SourceViewsMultiInsertState
|
|
||||||
# to insure marker processes inserted text correctly.
|
|
||||||
buffer.is_processing_completion = True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def do_get_activation(self):
|
|
||||||
""" The context for when a provider will show results """
|
|
||||||
# return GtkSource.CompletionActivation.NONE
|
|
||||||
return GtkSource.CompletionActivation.USER_REQUESTED
|
|
||||||
# return GtkSource.CompletionActivation.INTERACTIVE
|
|
||||||
|
|
||||||
def do_populate(self, context):
|
|
||||||
word = self.response_cache.get_word(context)
|
|
||||||
results = self.response_cache.filter(word)
|
|
||||||
proposals = []
|
|
||||||
|
|
||||||
for entry in results:
|
|
||||||
proposals.append(
|
|
||||||
self.response_cache.create_completion_item(
|
|
||||||
entry["label"],
|
|
||||||
entry["text"],
|
|
||||||
entry["info"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
context.add_proposals(self, proposals, True)
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GLib
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Code_Event_Types
|
|
||||||
|
|
||||||
from core.widgets.code.completion_providers.provider_response_cache_base import ProviderResponseCacheBase
|
|
||||||
|
|
||||||
from . import cson
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderResponseCache(ProviderResponseCacheBase):
|
|
||||||
def __init__(self):
|
|
||||||
super(ProviderResponseCache, self).__init__()
|
|
||||||
|
|
||||||
self.matchers: dict = {}
|
|
||||||
|
|
||||||
self.load_snippets()
|
|
||||||
|
|
||||||
|
|
||||||
def load_snippets(self):
|
|
||||||
fpath = path.join(path.dirname(path.realpath(__file__)), "snippets.cson")
|
|
||||||
with open(fpath, 'rb') as f:
|
|
||||||
self.snippets = cson.load(f)
|
|
||||||
for group in self.snippets:
|
|
||||||
self.snippets[group]
|
|
||||||
for entry in self.snippets[group]:
|
|
||||||
data = self.snippets[group][entry]
|
|
||||||
self.matchers[ data["prefix"] ] = {
|
|
||||||
"label": entry,
|
|
||||||
"text": data["body"],
|
|
||||||
"info": GLib.markup_escape_text( data["body"] )
|
|
||||||
}
|
|
||||||
|
|
||||||
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def filter(self, word: str) -> list[dict]:
|
|
||||||
response: list[dict] = []
|
|
||||||
|
|
||||||
for entry in self.matchers:
|
|
||||||
if not word in entry: continue
|
|
||||||
data = self.matchers[entry]
|
|
||||||
response.append(data)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def filter_with_context(self, context: GtkSource.CompletionContext) -> list[dict]:
|
|
||||||
response: list[dict] = []
|
|
||||||
|
|
||||||
return response
|
|
||||||
@@ -1,614 +0,0 @@
|
|||||||
# Your snippets
|
|
||||||
#
|
|
||||||
# Atom snippets allow you to enter a simple prefix in the editor and hit tab to
|
|
||||||
# expand the prefix into a larger code block with templated values.
|
|
||||||
#
|
|
||||||
# You can create a new snippet in this file by typing "snip" and then hitting
|
|
||||||
# tab.
|
|
||||||
#
|
|
||||||
# An example CoffeeScript snippet to expand log to console.log:
|
|
||||||
#
|
|
||||||
# '.source.coffee':
|
|
||||||
# 'Console log':
|
|
||||||
# 'prefix': 'log'
|
|
||||||
# 'body': 'console.log $1'
|
|
||||||
#
|
|
||||||
# Each scope (e.g. '.source.coffee' above) can only be declared once.
|
|
||||||
#
|
|
||||||
# This file uses CoffeeScript Object Notation (CSON).
|
|
||||||
# If you are unfamiliar with CSON, you can read more about it in the
|
|
||||||
# Atom Flight Manual:
|
|
||||||
# http://flight-manual.atom.io/using-atom/sections/basic-customization/#_cson
|
|
||||||
|
|
||||||
|
|
||||||
### HTML SNIPPETS ###
|
|
||||||
'.text.html.basic':
|
|
||||||
|
|
||||||
'HTML Template':
|
|
||||||
'prefix': 'html'
|
|
||||||
'body': """<!DOCTYPE html>
|
|
||||||
<html lang="en" dir="ltr">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title></title>
|
|
||||||
<link rel="shortcut icon" href="fave_icon.png">
|
|
||||||
<link rel="stylesheet" href="resources/css/base.css">
|
|
||||||
<link rel="stylesheet" href="resources/css/main.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script src="resources/js/.js" charset="utf-8"></script>
|
|
||||||
<script src="resources/js/.js" charset="utf-8"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
'Canvas Tag':
|
|
||||||
'prefix': 'canvas'
|
|
||||||
'body': """<canvas id="canvas" width="800" height="600" style="border:1px solid #c3c3c3;"></canvas>"""
|
|
||||||
|
|
||||||
'Img Tag':
|
|
||||||
'prefix': 'img'
|
|
||||||
'body': """<img class="" src="" alt="" />"""
|
|
||||||
|
|
||||||
'Br Tag':
|
|
||||||
'prefix': 'br'
|
|
||||||
'body': """<br/>"""
|
|
||||||
|
|
||||||
'Hr Tag':
|
|
||||||
'prefix': 'hr'
|
|
||||||
'body': """<hr/>"""
|
|
||||||
|
|
||||||
'Server Side Events':
|
|
||||||
'prefix': 'sse'
|
|
||||||
'body': """// SSE events if supported
|
|
||||||
if(typeof(EventSource) !== "undefined") {
|
|
||||||
let source = new EventSource("resources/php/sse.php");
|
|
||||||
source.onmessage = (event) => {
|
|
||||||
if (event.data === "<yourDataStringToLookFor>") {
|
|
||||||
// code here
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
console.log("SSE Not Supported In Browser...");
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
'AJAX Template Function':
|
|
||||||
'prefix': 'ajax template'
|
|
||||||
'body': """const doAjax = async (actionPath, data) => {
|
|
||||||
let xhttp = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState === 4 && this.status === 200) {
|
|
||||||
if (this.responseText != null) { // this.responseXML if getting XML fata
|
|
||||||
handleReturnData(JSON.parse(this.responseText));
|
|
||||||
} else {
|
|
||||||
console.log("No content returned. Check the file path.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.open("POST", actionPath, true);
|
|
||||||
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
|
||||||
// Force return to be JSON NOTE: Use application/xml to force XML
|
|
||||||
xhttp.overrideMimeType('application/json');
|
|
||||||
xhttp.send(data);
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
'CSS Message Colors':
|
|
||||||
'prefix': 'css colors'
|
|
||||||
'body': """.error { color: rgb(255, 0, 0); }
|
|
||||||
.warning { color: rgb(255, 168, 0); }
|
|
||||||
.success { color: rgb(136, 204, 39); }
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
### JS SNIPPETS ###
|
|
||||||
'.source.js':
|
|
||||||
|
|
||||||
'Server Side Events':
|
|
||||||
'prefix': 'sse'
|
|
||||||
'body': """// SSE events if supported
|
|
||||||
if(typeof(EventSource) !== "undefined") {
|
|
||||||
let source = new EventSource("resources/php/sse.php");
|
|
||||||
source.onmessage = (event) => {
|
|
||||||
if (event.data === "<yourDataStringToLookFor>") {
|
|
||||||
// code here
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
console.log("SSE Not Supported In Browser...");
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
'AJAX Template Function':
|
|
||||||
'prefix': 'ajax template'
|
|
||||||
'body': """const doAjax = async (actionPath, data) => {
|
|
||||||
let xhttp = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState === 4 && this.status === 200) {
|
|
||||||
if (this.responseText != null) { // this.responseXML if getting XML fata
|
|
||||||
handleReturnData(JSON.parse(this.responseText));
|
|
||||||
} else {
|
|
||||||
console.log("No content returned. Check the file path.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.open("POST", actionPath, true);
|
|
||||||
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
|
||||||
// Force return to be JSON NOTE: Use application/xml to force XML
|
|
||||||
xhttp.overrideMimeType('application/json');
|
|
||||||
xhttp.send(data);
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
'SE6 Function':
|
|
||||||
'prefix': 'function se6'
|
|
||||||
'body': """const funcName = (arg = "") => {
|
|
||||||
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
### CSS SNIPPETS ###
|
|
||||||
'.source.css':
|
|
||||||
|
|
||||||
'CSS Message Colors':
|
|
||||||
'prefix': 'css colors'
|
|
||||||
'body': """.error { color: rgb(255, 0, 0); }
|
|
||||||
.warning { color: rgb(255, 168, 0); }
|
|
||||||
.success { color: rgb(136, 204, 39); }
|
|
||||||
"""
|
|
||||||
|
|
||||||
### PHP SNIPPETS ###
|
|
||||||
'.text.html.php':
|
|
||||||
|
|
||||||
'SSE PHP':
|
|
||||||
'prefix': 'sse php'
|
|
||||||
'body': """<?php
|
|
||||||
// Start the session
|
|
||||||
session_start();
|
|
||||||
|
|
||||||
header('Content-Type: text/event-stream');
|
|
||||||
header('Cache-Control: no-cache');
|
|
||||||
|
|
||||||
echo "data:dataToReturn\\\\n\\\\n";
|
|
||||||
|
|
||||||
flush();
|
|
||||||
?>
|
|
||||||
"""
|
|
||||||
|
|
||||||
'PHP Template':
|
|
||||||
'prefix': 'php'
|
|
||||||
'body': """<?php
|
|
||||||
// Start the session
|
|
||||||
session_start();
|
|
||||||
|
|
||||||
|
|
||||||
// Determin action
|
|
||||||
chdir("../../"); // Note: If in resources/php/
|
|
||||||
if (isset($_POST['yourPostID'])) {
|
|
||||||
// code here
|
|
||||||
} else {
|
|
||||||
$message = "Server: [Error] --> Illegal Access Method!";
|
|
||||||
serverMessage("error", $message);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
"""
|
|
||||||
'HTML Template':
|
|
||||||
'prefix': 'html'
|
|
||||||
'body': """<!DOCTYPE html>
|
|
||||||
<html lang="en" dir="ltr">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title></title>
|
|
||||||
<link rel="shortcut icon" href="fave_icon.png">
|
|
||||||
<link rel="stylesheet" href="resources/css/base.css">
|
|
||||||
<link rel="stylesheet" href="resources/css/main.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script src="resources/js/.js" charset="utf-8"></script>
|
|
||||||
<script src="resources/js/.js" charset="utf-8"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
### BASH SNIPPETS ###
|
|
||||||
'.source.shell':
|
|
||||||
|
|
||||||
'Bash or Shell Template':
|
|
||||||
'prefix': 'bash template'
|
|
||||||
'body': """#!/bin/bash
|
|
||||||
|
|
||||||
# . CONFIG.sh
|
|
||||||
|
|
||||||
# set -o xtrace ## To debug scripts
|
|
||||||
# set -o errexit ## To exit on error
|
|
||||||
# set -o errunset ## To exit if a variable is referenced but not set
|
|
||||||
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
echo "Working Dir: " $(pwd)
|
|
||||||
|
|
||||||
file="$1"
|
|
||||||
if [ -z "${file}" ]; then
|
|
||||||
echo "ERROR: No file argument supplied..."
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -f "${file}" ]]; then
|
|
||||||
echo "SUCCESS: The path and file exists!"
|
|
||||||
else
|
|
||||||
echo "ERROR: The path or file '${file}' does NOT exist..."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
main "$@";
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
'Bash or Shell Config':
|
|
||||||
'prefix': 'bash config'
|
|
||||||
'body': """#!/bin/bash
|
|
||||||
|
|
||||||
shopt -s expand_aliases
|
|
||||||
|
|
||||||
alias echo="echo -e"
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
### PYTHON SNIPPETS ###
|
|
||||||
'.source.python':
|
|
||||||
|
|
||||||
'Glade __main__ Class Template':
|
|
||||||
'prefix': 'glade __main__ class'
|
|
||||||
'body': """#!/usr/bin/python3
|
|
||||||
|
|
||||||
|
|
||||||
# Python imports
|
|
||||||
import argparse
|
|
||||||
import faulthandler
|
|
||||||
import traceback
|
|
||||||
import signal
|
|
||||||
from setproctitle import setproctitle
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import GLib
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from app import Application
|
|
||||||
|
|
||||||
|
|
||||||
def run():
|
|
||||||
try:
|
|
||||||
setproctitle('<replace this>')
|
|
||||||
faulthandler.enable() # For better debug info
|
|
||||||
|
|
||||||
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, Gtk.main_quit)
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
# Add long and short arguments
|
|
||||||
parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.")
|
|
||||||
parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.")
|
|
||||||
parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.")
|
|
||||||
|
|
||||||
parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.")
|
|
||||||
parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.")
|
|
||||||
|
|
||||||
# Read arguments (If any...)
|
|
||||||
args, unknownargs = parser.parse_known_args()
|
|
||||||
|
|
||||||
main = Application(args, unknownargs)
|
|
||||||
Gtk.main()
|
|
||||||
except Exception as e:
|
|
||||||
traceback.print_exc()
|
|
||||||
quit()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
''' Set process title, get arguments, and create GTK main thread. '''
|
|
||||||
run()
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
'Glade __main__ Testing Template':
|
|
||||||
'prefix': 'glade testing class'
|
|
||||||
'body': """#!/usr/bin/python3
|
|
||||||
|
|
||||||
|
|
||||||
# Python imports
|
|
||||||
import traceback
|
|
||||||
import faulthandler
|
|
||||||
import signal
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import GLib
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
app_name = "Gtk Quick Test"
|
|
||||||
|
|
||||||
|
|
||||||
class Application(Gtk.ApplicationWindow):
|
|
||||||
def __init__(self):
|
|
||||||
super(Application, self).__init__()
|
|
||||||
self._setup_styling()
|
|
||||||
self._setup_signals()
|
|
||||||
self._load_widgets()
|
|
||||||
|
|
||||||
self.add(Gtk.Box())
|
|
||||||
|
|
||||||
self.show_all()
|
|
||||||
|
|
||||||
|
|
||||||
def _setup_styling(self):
|
|
||||||
self.set_default_size(1670, 830)
|
|
||||||
self.set_title(f"{app_name}")
|
|
||||||
# self.set_icon_from_file( settings.get_window_icon() )
|
|
||||||
self.set_gravity(5) # 5 = CENTER
|
|
||||||
self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS
|
|
||||||
|
|
||||||
def _setup_signals(self):
|
|
||||||
self.connect("delete-event", Gtk.main_quit)
|
|
||||||
|
|
||||||
|
|
||||||
def _load_widgets(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run():
|
|
||||||
try:
|
|
||||||
faulthandler.enable() # For better debug info
|
|
||||||
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, Gtk.main_quit)
|
|
||||||
|
|
||||||
main = Application()
|
|
||||||
Gtk.main()
|
|
||||||
except Exception as e:
|
|
||||||
traceback.print_exc()
|
|
||||||
quit()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
''' Set process title, get arguments, and create GTK main thread. '''
|
|
||||||
run()
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
'Glade _init_ Class Template':
|
|
||||||
'prefix': 'glade __init__ class'
|
|
||||||
'body': """# Python imports
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from utils import Settings
|
|
||||||
from signal_classes import CrossClassSignals
|
|
||||||
|
|
||||||
|
|
||||||
class Main:
|
|
||||||
def __init__(self, args):
|
|
||||||
settings = Settings()
|
|
||||||
builder = settings.returnBuilder()
|
|
||||||
|
|
||||||
# Gets the methods from the classes and sets to handler.
|
|
||||||
# Then, builder connects to any signals it needs.
|
|
||||||
classes = [CrossClassSignals(settings)]
|
|
||||||
|
|
||||||
handlers = {}
|
|
||||||
for c in classes:
|
|
||||||
methods = inspect.getmembers(c, predicate=inspect.ismethod)
|
|
||||||
handlers.update(methods)
|
|
||||||
|
|
||||||
builder.connect_signals(handlers)
|
|
||||||
window = settings.createWindow()
|
|
||||||
window.show()
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
'Class Method':
|
|
||||||
'prefix': 'def1'
|
|
||||||
'body': """
|
|
||||||
def fname(self):
|
|
||||||
...
|
|
||||||
"""
|
|
||||||
|
|
||||||
'Gtk Class Method':
|
|
||||||
'prefix': 'def2'
|
|
||||||
'body': """
|
|
||||||
def fname(self, widget, eve):
|
|
||||||
...
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
'Python Glade Settings Template':
|
|
||||||
'prefix': 'glade settings class'
|
|
||||||
'body': """# Python imports
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi, cairo
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
gi.require_version('Gdk', '3.0')
|
|
||||||
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import Gdk
|
|
||||||
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
class Settings:
|
|
||||||
def __init__(self):
|
|
||||||
self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + "/"
|
|
||||||
self.builder = Gtk.Builder()
|
|
||||||
self.builder.add_from_file(self.SCRIPT_PTH + "../resources/Main_Window.glade")
|
|
||||||
|
|
||||||
# 'Filters'
|
|
||||||
self.office = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx', '.xlm',
|
|
||||||
'.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf')
|
|
||||||
self.vids = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv',
|
|
||||||
'.mpeg', '.mp4', '.webm')
|
|
||||||
self.txt = ('.txt', '.text', '.sh', '.cfg', '.conf')
|
|
||||||
self.music = ('.psf', '.mp3', '.ogg' , '.flac')
|
|
||||||
self.images = ('.png', '.jpg', '.jpeg', '.gif')
|
|
||||||
self.pdf = ('.pdf')
|
|
||||||
|
|
||||||
|
|
||||||
def createWindow(self):
|
|
||||||
# Get window and connect signals
|
|
||||||
window = self.builder.get_object("Main_Window")
|
|
||||||
window.connect("delete-event", gtk.main_quit)
|
|
||||||
self.setWindowData(window, False)
|
|
||||||
return window
|
|
||||||
|
|
||||||
def setWindowData(self, window, paintable):
|
|
||||||
screen = window.get_screen()
|
|
||||||
visual = screen.get_rgba_visual()
|
|
||||||
|
|
||||||
if visual != None and screen.is_composited():
|
|
||||||
window.set_visual(visual)
|
|
||||||
|
|
||||||
# bind css file
|
|
||||||
cssProvider = gtk.CssProvider()
|
|
||||||
cssProvider.load_from_path(self.SCRIPT_PTH + '../resources/stylesheet.css')
|
|
||||||
screen = Gdk.Screen.get_default()
|
|
||||||
styleContext = Gtk.StyleContext()
|
|
||||||
styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER)
|
|
||||||
|
|
||||||
window.set_app_paintable(paintable)
|
|
||||||
if paintable:
|
|
||||||
window.connect("draw", self.area_draw)
|
|
||||||
|
|
||||||
def getMonitorData(self):
|
|
||||||
screen = self.builder.get_object("Main_Window").get_screen()
|
|
||||||
monitors = []
|
|
||||||
for m in range(screen.get_n_monitors()):
|
|
||||||
monitors.append(screen.get_monitor_geometry(m))
|
|
||||||
|
|
||||||
for monitor in monitors:
|
|
||||||
print(str(monitor.width) + "x" + str(monitor.height) + "+" + str(monitor.x) + "+" + str(monitor.y))
|
|
||||||
|
|
||||||
return monitors
|
|
||||||
|
|
||||||
def area_draw(self, widget, cr):
|
|
||||||
cr.set_source_rgba(0, 0, 0, 0.54)
|
|
||||||
cr.set_operator(cairo.OPERATOR_SOURCE)
|
|
||||||
cr.paint()
|
|
||||||
cr.set_operator(cairo.OPERATOR_OVER)
|
|
||||||
|
|
||||||
|
|
||||||
def returnBuilder(self): return self.builder
|
|
||||||
|
|
||||||
# Filter returns
|
|
||||||
def returnOfficeFilter(self): return self.office
|
|
||||||
def returnVidsFilter(self): return self.vids
|
|
||||||
def returnTextFilter(self): return self.txt
|
|
||||||
def returnMusicFilter(self): return self.music
|
|
||||||
def returnImagesFilter(self): return self.images
|
|
||||||
def returnPdfFilter(self): return self.pdf
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
'Python Glade CrossClassSignals Template':
|
|
||||||
'prefix': 'glade crossClassSignals class'
|
|
||||||
'body': """# Python imports
|
|
||||||
import threading
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
def threaded(fn):
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
threading.Thread(target=fn, args=args, kwargs=kwargs).start()
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
class CrossClassSignals:
|
|
||||||
def __init__(self, settings):
|
|
||||||
self.settings = settings
|
|
||||||
self.builder = self.settings.returnBuilder()
|
|
||||||
|
|
||||||
|
|
||||||
def getClipboardData(self):
|
|
||||||
proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE)
|
|
||||||
retcode = proc.wait()
|
|
||||||
data = proc.stdout.read()
|
|
||||||
return data.decode("utf-8").strip()
|
|
||||||
|
|
||||||
def setClipboardData(self, data):
|
|
||||||
proc = subprocess.Popen(['xclip','-selection','clipboard'], stdin=subprocess.PIPE)
|
|
||||||
proc.stdin.write(data)
|
|
||||||
proc.stdin.close()
|
|
||||||
retcode = proc.wait()
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
'Python Glade Generic Template':
|
|
||||||
'prefix': 'glade generic class'
|
|
||||||
'body': """# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
class GenericClass:
|
|
||||||
def __init__(self):
|
|
||||||
super(GenericClass, self).__init__()
|
|
||||||
|
|
||||||
self._setup_styling()
|
|
||||||
self._setup_signals()
|
|
||||||
self._subscribe_to_events()
|
|
||||||
self._load_widgets()
|
|
||||||
|
|
||||||
|
|
||||||
def _setup_styling(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def _setup_signals(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def _subscribe_to_events(self):
|
|
||||||
event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
|
|
||||||
|
|
||||||
def _load_widgets(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Module
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Package
|
|
||||||
"""
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import re
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GObject
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from .provider_response_cache import ProviderResponseCache
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Provider(GtkSource.CompletionWords):
|
|
||||||
"""
|
|
||||||
This is a Words Completion Provider.
|
|
||||||
# NOTE: used information from here --> https://warroom.rsmus.com/do-that-auto-complete/
|
|
||||||
"""
|
|
||||||
__gtype_name__ = 'WordsCompletionProvider'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(Provider, self).__init__()
|
|
||||||
|
|
||||||
self.response_cache: ProviderResponseCache = ProviderResponseCache()
|
|
||||||
|
|
||||||
|
|
||||||
def do_get_name(self):
|
|
||||||
return 'Words Completion'
|
|
||||||
|
|
||||||
def do_match(self, context):
|
|
||||||
# Note: If provider is in interactive activation then need to check
|
|
||||||
# view focus as otherwise non focus views start trying to grab it.
|
|
||||||
completion = context.get_property("completion")
|
|
||||||
if not completion.get_view().has_focus(): return
|
|
||||||
|
|
||||||
word = self.response_cache.get_word(context)
|
|
||||||
if not word or len(word) < 2: return False
|
|
||||||
|
|
||||||
iter = self.response_cache.get_iter_correctly(context)
|
|
||||||
iter.backward_char()
|
|
||||||
ch = iter.get_char()
|
|
||||||
# NOTE: Look to re-add or apply supprting logic to use spaces
|
|
||||||
# As is it slows down the editor in certain contexts...
|
|
||||||
# if not (ch in ('_', '.', ' ') or ch.isalnum()):
|
|
||||||
if not (ch in ('_', '.') or ch.isalnum()):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def do_get_priority(self):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def do_activate_proposal(self, proposal, iter_):
|
|
||||||
buffer = iter_.get_buffer()
|
|
||||||
# Note: Flag mostly intended for SourceViewsMultiInsertState
|
|
||||||
# to insure marker processes inserted text correctly.
|
|
||||||
buffer.is_processing_completion = True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def do_get_activation(self):
|
|
||||||
""" The context for when a provider will show results """
|
|
||||||
# return GtkSource.CompletionActivation.NONE
|
|
||||||
# return GtkSource.CompletionActivation.USER_REQUESTED
|
|
||||||
return GtkSource.CompletionActivation.INTERACTIVE
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Code_Event_Types
|
|
||||||
|
|
||||||
from core.widgets.code.completion_providers.provider_response_cache_base import ProviderResponseCacheBase
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderResponseCache(ProviderResponseCacheBase):
|
|
||||||
def __init__(self):
|
|
||||||
super(ProviderResponseCache, self).__init__()
|
|
||||||
|
|
||||||
self.matchers: dict = {}
|
|
||||||
|
|
||||||
|
|
||||||
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
|
||||||
...
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Words Completer",
|
|
||||||
"author": "ITDominator",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"support": "",
|
|
||||||
"requests": {}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
|
||||||
|
|
||||||
from .provider import Provider
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginCode):
|
|
||||||
def __init__(self):
|
|
||||||
super(Plugin, self).__init__()
|
|
||||||
|
|
||||||
self.provider: Provider = None
|
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
self.provider = Provider()
|
|
||||||
|
|
||||||
event = Event_Factory.create_event(
|
|
||||||
"register_provider",
|
|
||||||
provider_name = "Words Completer",
|
|
||||||
provider = self.provider,
|
|
||||||
language_ids = []
|
|
||||||
)
|
|
||||||
self.emit_to("completion", event)
|
|
||||||
|
|
||||||
def unload(self):
|
|
||||||
event = Event_Factory.create_event(
|
|
||||||
"unregister_provider",
|
|
||||||
provider_name = "Words Completer"
|
|
||||||
)
|
|
||||||
self.emit_to("completion", event)
|
|
||||||
|
|
||||||
self.provider = None
|
|
||||||
del self.provider
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
...
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import re
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GObject
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from .provider_response_cache import ProviderResponseCache
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Provider(GObject.GObject, GtkSource.CompletionProvider):
|
|
||||||
"""
|
|
||||||
This is a Words Completion Provider.
|
|
||||||
# NOTE: used information from here --> https://warroom.rsmus.com/do-that-auto-complete/
|
|
||||||
"""
|
|
||||||
__gtype_name__ = 'WordsCompletionProvider'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(Provider, self).__init__()
|
|
||||||
|
|
||||||
self.response_cache: ProviderResponseCache = ProviderResponseCache()
|
|
||||||
|
|
||||||
|
|
||||||
def do_get_name(self):
|
|
||||||
return 'Words Completion'
|
|
||||||
|
|
||||||
def do_match(self, context):
|
|
||||||
# Note: If provider is in interactive activation then need to check
|
|
||||||
# view focus as otherwise non focus views start trying to grab it.
|
|
||||||
completion = context.get_property("completion")
|
|
||||||
if not completion.get_view().has_focus(): return
|
|
||||||
|
|
||||||
word = self.response_cache.get_word(context)
|
|
||||||
if not word or len(word) < 2: return False
|
|
||||||
|
|
||||||
iter = self.response_cache.get_iter_correctly(context)
|
|
||||||
iter.backward_char()
|
|
||||||
ch = iter.get_char()
|
|
||||||
# NOTE: Look to re-add or apply supprting logic to use spaces
|
|
||||||
# As is it slows down the editor in certain contexts...
|
|
||||||
# if not (ch in ('_', '.', ' ') or ch.isalnum()):
|
|
||||||
if not (ch in ('_', '.') or ch.isalnum()):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def do_get_priority(self):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def do_activate_proposal(self, proposal, iter_):
|
|
||||||
buffer = iter_.get_buffer()
|
|
||||||
# Note: Flag mostly intended for SourceViewsMultiInsertState
|
|
||||||
# to insure marker processes inserted text correctly.
|
|
||||||
buffer.is_processing_completion = True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def do_get_activation(self):
|
|
||||||
""" The context for when a provider will show results """
|
|
||||||
# return GtkSource.CompletionActivation.NONE
|
|
||||||
# return GtkSource.CompletionActivation.USER_REQUESTED
|
|
||||||
return GtkSource.CompletionActivation.INTERACTIVE
|
|
||||||
|
|
||||||
def do_populate(self, context):
|
|
||||||
word = self.response_cache.get_word(context)
|
|
||||||
results = self.response_cache.filter_with_context(context)
|
|
||||||
# results = self.response_cache.filter(word)
|
|
||||||
|
|
||||||
# if not results:
|
|
||||||
# results = self.response_cache.filter_with_context(context)
|
|
||||||
|
|
||||||
proposals = []
|
|
||||||
for entry in results:
|
|
||||||
proposals.append(
|
|
||||||
self.response_cache.create_completion_item(
|
|
||||||
entry["label"],
|
|
||||||
entry["text"],
|
|
||||||
entry["info"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
context.add_proposals(self, proposals, True)
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GLib
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Code_Event_Types
|
|
||||||
|
|
||||||
from core.widgets.code.completion_providers.provider_response_cache_base import ProviderResponseCacheBase
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderResponseCache(ProviderResponseCacheBase):
|
|
||||||
def __init__(self):
|
|
||||||
super(ProviderResponseCache, self).__init__()
|
|
||||||
|
|
||||||
self.matchers: dict = {}
|
|
||||||
self._temp_timeout_id: int = None
|
|
||||||
|
|
||||||
|
|
||||||
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
|
|
||||||
buffer = event.file.buffer
|
|
||||||
with ThreadPoolExecutor(max_workers = 1) as executor:
|
|
||||||
executor.submit(self._handle_change, buffer)
|
|
||||||
|
|
||||||
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
|
|
||||||
self.matchers[event.file.buffer] = set()
|
|
||||||
del self.matchers[event.file.buffer]
|
|
||||||
|
|
||||||
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
|
||||||
buffer = event.file.buffer
|
|
||||||
self._clear_temp_delay()
|
|
||||||
self._set_temp_delay(buffer)
|
|
||||||
|
|
||||||
def _clear_temp_delay(self):
|
|
||||||
if self._temp_timeout_id:
|
|
||||||
GLib.source_remove(self._temp_timeout_id)
|
|
||||||
|
|
||||||
def _set_temp_delay(self, buffer):
|
|
||||||
def run_refresh_update(buffer):
|
|
||||||
with ThreadPoolExecutor(max_workers = 1) as executor:
|
|
||||||
executor.submit(self._handle_change, buffer)
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
self._temp_timeout_id = GLib.timeout_add(1500, run_refresh_update, buffer)
|
|
||||||
|
|
||||||
def _handle_change(self, buffer):
|
|
||||||
start_itr = buffer.get_start_iter()
|
|
||||||
end_itr = buffer.get_end_iter()
|
|
||||||
data = buffer.get_text(start_itr, end_itr, False)
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
GLib.idle_add(self.load_empty_set, buffer)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not buffer in self.matchers:
|
|
||||||
GLib.idle_add(self.load_as_new_set, buffer, data)
|
|
||||||
return
|
|
||||||
|
|
||||||
new_words = self.get_all_words(data)
|
|
||||||
GLib.idle_add(self.load_into_set, buffer, new_words)
|
|
||||||
|
|
||||||
def filter(self, word: str) -> list[dict]:
|
|
||||||
response: list[dict] = []
|
|
||||||
|
|
||||||
for entry in self.matchers:
|
|
||||||
if not word in entry: continue
|
|
||||||
data = self.matchers[entry]
|
|
||||||
response.append(data)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def filter_with_context(self, context: GtkSource.CompletionContext) -> list[dict]:
|
|
||||||
buffer = self.get_iter_correctly(context).get_buffer()
|
|
||||||
word = self.get_word(context).rstrip()
|
|
||||||
|
|
||||||
if not buffer in self.matchers:
|
|
||||||
self.matchers[buffer] = set()
|
|
||||||
|
|
||||||
response: list[dict] = []
|
|
||||||
for entry in self.matchers[buffer]:
|
|
||||||
if not entry.rstrip().startswith(word): continue
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"label": entry,
|
|
||||||
"text": entry,
|
|
||||||
"info": ""
|
|
||||||
}
|
|
||||||
|
|
||||||
response.append(data)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def load_empty_set(self, buffer):
|
|
||||||
self.matchers[buffer] = set()
|
|
||||||
|
|
||||||
def load_into_set(self, buffer, new_words):
|
|
||||||
# self.matchers[buffer].update(new_words)
|
|
||||||
self.matchers[buffer] = new_words
|
|
||||||
|
|
||||||
def load_as_new_set(self, buffer, data):
|
|
||||||
self.matchers[buffer] = self.get_all_words(data)
|
|
||||||
|
|
||||||
def get_all_words(self, data: str):
|
|
||||||
words = set()
|
|
||||||
|
|
||||||
def is_word_char(c):
|
|
||||||
return c.isalnum() or c == '_'
|
|
||||||
|
|
||||||
size = len(data)
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
while i < size:
|
|
||||||
# Skip non-word characters
|
|
||||||
while i < size and not is_word_char(data[i]):
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
start = i
|
|
||||||
# Consume word characters
|
|
||||||
while i < size and is_word_char(data[i]):
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
word = data[start:i]
|
|
||||||
if not word: continue
|
|
||||||
words.add(word)
|
|
||||||
|
|
||||||
return words
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Plugin Module
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Plugin Package
|
|
||||||
"""
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Extend Source View Menu",
|
|
||||||
"author": "ITDominator",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"support": "",
|
|
||||||
"requests": {}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
|
||||||
|
|
||||||
from .source_view_menu import extend_source_view_menu
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginCode):
|
|
||||||
def __init__(self):
|
|
||||||
super(Plugin, self).__init__()
|
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
|
||||||
if isinstance(event, Code_Event_Types.PopulateSourceViewPopupEvent):
|
|
||||||
extend_source_view_menu(event.buffer, event.menu)
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def unload(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
...
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def on_case_handle(menuitem, buffer, action):
|
|
||||||
start_itr, \
|
|
||||||
end_itr = buffer.get_selection_bounds()
|
|
||||||
data = buffer.get_text(start_itr, end_itr, False)
|
|
||||||
text = data
|
|
||||||
|
|
||||||
if action == "on_all_upper":
|
|
||||||
text = data.upper()
|
|
||||||
elif action == "on_all_lower":
|
|
||||||
text = data.lower()
|
|
||||||
elif action == "on_invert":
|
|
||||||
text = data.swapcase()
|
|
||||||
elif action == "on_title":
|
|
||||||
text = data.title()
|
|
||||||
elif action == "on_title_strip":
|
|
||||||
text = data.title().replace("-", "").replace("_", "").replace(" ", "")
|
|
||||||
|
|
||||||
buffer.begin_user_action()
|
|
||||||
buffer.delete(start_itr, end_itr)
|
|
||||||
buffer.insert(start_itr, text)
|
|
||||||
buffer.end_user_action()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def extend_source_view_menu(buffer, menu):
|
|
||||||
if not buffer.get_selection_bounds(): return
|
|
||||||
|
|
||||||
for child in menu.get_children():
|
|
||||||
if not child.get_label() == "C_hange Case": continue
|
|
||||||
menu.remove(child)
|
|
||||||
|
|
||||||
change_case_item = Gtk.MenuItem(label = "Change Case")
|
|
||||||
|
|
||||||
case_menu = Gtk.Menu()
|
|
||||||
au_case_item = Gtk.MenuItem(label = "All Upper Case")
|
|
||||||
al_case_item = Gtk.MenuItem(label = "All Lower Case")
|
|
||||||
inver_case_item = Gtk.MenuItem(label = "Invert Case")
|
|
||||||
title_case_item = Gtk.MenuItem(label = "Title Case")
|
|
||||||
title_strip_case_item = Gtk.MenuItem(label = "Title Strip Case")
|
|
||||||
|
|
||||||
au_case_item.connect("activate", on_case_handle, buffer, "on_all_upper")
|
|
||||||
al_case_item.connect("activate", on_case_handle, buffer, "on_all_lower")
|
|
||||||
inver_case_item.connect("activate", on_case_handle, buffer, "on_invert")
|
|
||||||
title_case_item.connect("activate", on_case_handle, buffer, "on_title")
|
|
||||||
title_strip_case_item.connect("activate", on_case_handle, buffer, "on_title_strip")
|
|
||||||
|
|
||||||
case_menu.append(au_case_item)
|
|
||||||
case_menu.append(al_case_item)
|
|
||||||
case_menu.append(inver_case_item)
|
|
||||||
case_menu.append(title_case_item)
|
|
||||||
case_menu.append(title_strip_case_item)
|
|
||||||
change_case_item.set_submenu(case_menu)
|
|
||||||
|
|
||||||
menu.append(change_case_item)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Module
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Package
|
|
||||||
"""
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "File State Watcher",
|
|
||||||
"author": "ITDominator",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"support": "",
|
|
||||||
"requests": {}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
|
||||||
|
|
||||||
from .watcher_checks import *
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginCode):
|
|
||||||
def __init__(self):
|
|
||||||
super(Plugin, self).__init__()
|
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
|
||||||
if not isinstance(event, Code_Event_Types.FocusedViewEvent): return
|
|
||||||
event = Event_Factory.create_event(
|
|
||||||
"get_file", buffer = event.view.get_buffer()
|
|
||||||
)
|
|
||||||
self.emit_to("files", event)
|
|
||||||
|
|
||||||
file = event.response
|
|
||||||
if not file: return
|
|
||||||
if file.ftype == "buffer": return
|
|
||||||
|
|
||||||
file.check_file_on_disk()
|
|
||||||
|
|
||||||
if file.is_deleted():
|
|
||||||
file_is_deleted(file, self.emit)
|
|
||||||
elif file.is_externally_modified():
|
|
||||||
file_is_externally_modified(file, self.emit)
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def unload(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
...
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def ask_yes_no(message):
|
|
||||||
dialog = Gtk.MessageDialog(
|
|
||||||
parent = None,
|
|
||||||
flags = 0,
|
|
||||||
message_type = Gtk.MessageType.QUESTION,
|
|
||||||
buttons = Gtk.ButtonsType.YES_NO,
|
|
||||||
text = message,
|
|
||||||
)
|
|
||||||
dialog.set_title("Confirm")
|
|
||||||
|
|
||||||
response = dialog.run()
|
|
||||||
dialog.destroy()
|
|
||||||
|
|
||||||
return response == Gtk.ResponseType.YES
|
|
||||||
|
|
||||||
|
|
||||||
def file_is_deleted(file, emit):
|
|
||||||
file.was_deleted = True
|
|
||||||
event = Event_Factory.create_event(
|
|
||||||
"file_externally_deleted",
|
|
||||||
file = file,
|
|
||||||
buffer = file.buffer
|
|
||||||
)
|
|
||||||
emit(event)
|
|
||||||
|
|
||||||
|
|
||||||
def file_is_externally_modified(file, emit):
|
|
||||||
event = Event_Factory.create_event(
|
|
||||||
"file_externally_modified",
|
|
||||||
file = file,
|
|
||||||
buffer = file.buffer
|
|
||||||
)
|
|
||||||
emit(event)
|
|
||||||
|
|
||||||
if not file.buffer.get_modified():
|
|
||||||
file.reload()
|
|
||||||
return
|
|
||||||
|
|
||||||
result = ask_yes_no("File has been externally modified. Reload?")
|
|
||||||
if not result: return
|
|
||||||
|
|
||||||
file.reload()
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Module
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Package
|
|
||||||
"""
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Prettify JSON",
|
|
||||||
"author": "ITDominator",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"support": "",
|
|
||||||
"requests": {}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
|
||||||
|
|
||||||
from .prettify_json import add_prettify_json
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginCode):
|
|
||||||
def __init__(self):
|
|
||||||
super(Plugin, self).__init__()
|
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
|
||||||
if isinstance(event, Code_Event_Types.PopulateSourceViewPopupEvent):
|
|
||||||
language = event.buffer.get_language()
|
|
||||||
if not language: return
|
|
||||||
|
|
||||||
if "json" == language.get_id():
|
|
||||||
add_prettify_json(event.buffer, event.menu)
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def unload(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
...
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def add_prettify_json(buffer, menu):
|
|
||||||
def on_prettify_json(menuitem, buffer):
|
|
||||||
start_itr, \
|
|
||||||
end_itr = buffer.get_start_iter(), buffer.get_end_iter()
|
|
||||||
data = buffer.get_text(start_itr, end_itr, False)
|
|
||||||
text = json.dumps(json.loads(data), separators = (',', ':'), indent = 4)
|
|
||||||
|
|
||||||
buffer.begin_user_action()
|
|
||||||
buffer.delete(start_itr, end_itr)
|
|
||||||
buffer.insert(start_itr, text)
|
|
||||||
buffer.end_user_action()
|
|
||||||
|
|
||||||
item = Gtk.MenuItem(label = "Prettify JSON")
|
|
||||||
item.connect("activate", on_prettify_json, buffer)
|
|
||||||
menu.append(item)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Module
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Package
|
|
||||||
"""
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
{
|
|
||||||
"info": "https://download.eclipse.org/jdtls/",
|
|
||||||
"info-init-options": "https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line",
|
|
||||||
"info-import-build": "https://www.javahotchocolate.com/tutorials/build-path.html",
|
|
||||||
"info-external-class-paths": "https://github.com/eclipse-jdtls/eclipse.jdt.ls/issues/3291",
|
|
||||||
"link": "https://download.eclipse.org/jdtls/milestones/?d",
|
|
||||||
"command": "lsp-ws-proxy --listen 4114 -- jdtls",
|
|
||||||
"alt-command": "lsp-ws-proxy -- jdtls",
|
|
||||||
"alt-command2": "java-language-server",
|
|
||||||
"socket": "ws://127.0.0.1:9999/java",
|
|
||||||
"socket-two": "ws://127.0.0.1:9999/?name=jdtls",
|
|
||||||
"alt-socket": "ws://127.0.0.1:9999/?name=java-language-server",
|
|
||||||
"initialization-options": {
|
|
||||||
"bundles": [
|
|
||||||
"intellicode-core.jar"
|
|
||||||
],
|
|
||||||
"workspaceFolders": [
|
|
||||||
"file://{workspace.folder}"
|
|
||||||
],
|
|
||||||
"extendedClientCapabilities": {
|
|
||||||
"classFileContentsSupport": true,
|
|
||||||
"executeClientCommandSupport": false
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"java": {
|
|
||||||
"autobuild": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"jdt": {
|
|
||||||
"ls": {
|
|
||||||
"javac": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"java": {
|
|
||||||
"home": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2"
|
|
||||||
},
|
|
||||||
"lombokSupport": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"protobufSupport":{
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"androidSupport": {
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"configuration": {
|
|
||||||
"updateBuildConfiguration": "automatic",
|
|
||||||
"maven": {
|
|
||||||
"userSettings": "{user.home}/.config/jdtls/settings.xml",
|
|
||||||
"globalSettings": "{user.home}/.config/jdtls/settings.xml"
|
|
||||||
},
|
|
||||||
"runtimes": [
|
|
||||||
{
|
|
||||||
"name": "JavaSE-17",
|
|
||||||
"path": "/usr/lib/jvm/java-17-openjdk",
|
|
||||||
"javadoc": "https://docs.oracle.com/en/java/javase/17/docs/api/",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "JavaSE-22",
|
|
||||||
"path": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2",
|
|
||||||
"javadoc": "https://docs.oracle.com/en/java/javase/22/docs/api/",
|
|
||||||
"default": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"classPath": [
|
|
||||||
"{user.home}/.config/jdtls/m2/repository/**/*-sources.jar",
|
|
||||||
"lib/**/*-sources.jar"
|
|
||||||
],
|
|
||||||
"docPath": [
|
|
||||||
"{user.home}/.config/jdtls/m2/repository/**/*-javadoc.jar",
|
|
||||||
"lib/**/*-javadoc.jar"
|
|
||||||
],
|
|
||||||
"project": {
|
|
||||||
"encoding": "ignore",
|
|
||||||
"outputPath": "bin",
|
|
||||||
"referencedLibraries": [
|
|
||||||
"{user.home}/.config/jdtls/m2/repository/**/*.jar",
|
|
||||||
"lib/**/*.jar"
|
|
||||||
],
|
|
||||||
"importOnFirstTimeStartup": "automatic",
|
|
||||||
"importHint": true,
|
|
||||||
"resourceFilters": [
|
|
||||||
"node_modules",
|
|
||||||
"\\.git"
|
|
||||||
],
|
|
||||||
"sourcePaths": [
|
|
||||||
"src",
|
|
||||||
"{user.home}/.config/jdtls/m2/repository/**/*.jar"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sources": {
|
|
||||||
"organizeImports": {
|
|
||||||
"starThreshold": 99,
|
|
||||||
"staticStarThreshold": 99
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"imports": {
|
|
||||||
"gradle": {
|
|
||||||
"wrapper": {
|
|
||||||
"checksums": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"import": {
|
|
||||||
"maven": {
|
|
||||||
"enabled": true,
|
|
||||||
"offline": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"disableTestClasspathFlag": false
|
|
||||||
},
|
|
||||||
"gradle": {
|
|
||||||
"enabled": false,
|
|
||||||
"wrapper": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"version": "",
|
|
||||||
"home": "abs(static/gradle-7.3.3)",
|
|
||||||
"java": {
|
|
||||||
"home": "abs(static/launch_jres/17.0.6-linux-x86_64)"
|
|
||||||
},
|
|
||||||
"offline": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"arguments": [],
|
|
||||||
"jvmArguments": [],
|
|
||||||
"user": {
|
|
||||||
"home": ""
|
|
||||||
},
|
|
||||||
"annotationProcessing": {
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"exclusions": [
|
|
||||||
"**/node_modules/**",
|
|
||||||
"**/.metadata/**",
|
|
||||||
"**/archetype-resources/**",
|
|
||||||
"**/META-INF/maven/**"
|
|
||||||
],
|
|
||||||
"generatesMetadataFilesAtProjectRoot": false
|
|
||||||
},
|
|
||||||
"maven": {
|
|
||||||
"downloadSources": true,
|
|
||||||
"updateSnapshots": true
|
|
||||||
},
|
|
||||||
"silentNotification": true,
|
|
||||||
"contentProvider": {
|
|
||||||
"preferred": "fernflower"
|
|
||||||
},
|
|
||||||
"signatureHelp": {
|
|
||||||
"enabled": true,
|
|
||||||
"description": {
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"completion": {
|
|
||||||
"enabled": true,
|
|
||||||
"engine": "ecj",
|
|
||||||
"matchCase": "firstletter",
|
|
||||||
"maxResults": 25,
|
|
||||||
"guessMethodArguments": true,
|
|
||||||
"lazyResolveTextEdit": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"postfix": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"favoriteStaticMembers": [
|
|
||||||
"org.junit.Assert.*",
|
|
||||||
"org.junit.Assume.*",
|
|
||||||
"org.junit.jupiter.api.Assertions.*",
|
|
||||||
"org.junit.jupiter.api.Assumptions.*",
|
|
||||||
"org.junit.jupiter.api.DynamicContainer.*",
|
|
||||||
"org.junit.jupiter.api.DynamicTest.*"
|
|
||||||
],
|
|
||||||
"importOrder": [
|
|
||||||
"#",
|
|
||||||
"java",
|
|
||||||
"javax",
|
|
||||||
"org",
|
|
||||||
"com"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"references": {
|
|
||||||
"includeAccessors": true,
|
|
||||||
"includeDecompiledSources": true
|
|
||||||
},
|
|
||||||
"codeGeneration": {
|
|
||||||
"toString": {
|
|
||||||
"template": "${object.className}{${member.name()}=${member.value}, ${otherMembers}}"
|
|
||||||
},
|
|
||||||
"insertionLocation": "afterCursor",
|
|
||||||
"useBlocks": true
|
|
||||||
},
|
|
||||||
"implementationsCodeLens": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"referencesCodeLens": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"progressReports": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"saveActions": {
|
|
||||||
"organizeImports": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Java LSP Client",
|
|
||||||
"author": "ITDominator",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"support": "",
|
|
||||||
"autoload": false,
|
|
||||||
"requests": {}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
from gi.repository import GLib
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
|
||||||
|
|
||||||
from .response_handler import JavaHandler
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginCode):
|
|
||||||
def __init__(self):
|
|
||||||
super(Plugin, self).__init__()
|
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
|
||||||
...
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
dirPth = path.dirname( path.realpath(__file__) )
|
|
||||||
with open(f"{dirPth}/config/lsp-server-config.json", "r") as f:
|
|
||||||
config = f.read()
|
|
||||||
event = Event_Factory.create_event("register_lsp_client",
|
|
||||||
lang_id = "java",
|
|
||||||
lang_config = config,
|
|
||||||
handler = JavaHandler
|
|
||||||
)
|
|
||||||
self.emit_to("lsp_manager", event)
|
|
||||||
|
|
||||||
def unload(self):
|
|
||||||
event = Event_Factory.create_event("unregister_lsp_client",
|
|
||||||
lang_id = "java"
|
|
||||||
)
|
|
||||||
self.emit_to("lsp_manager", event)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
...
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
from .java import JavaHandler
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from lsp_manager.response_handlers.default import DefaultHandler
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class JavaHandler(DefaultHandler):
|
|
||||||
"""Java-specific: overrides definition, handles classFileContents."""
|
|
||||||
|
|
||||||
def handle(self, method: str, response, controller):
|
|
||||||
match method:
|
|
||||||
case "textDocument/definition":
|
|
||||||
self._handle_definition(response, controller)
|
|
||||||
case "java/classFileContents":
|
|
||||||
self._handle_class_file_contents(response)
|
|
||||||
case _:
|
|
||||||
super().handle(method, response, controller)
|
|
||||||
|
|
||||||
def _handle_definition(self, response, controller):
|
|
||||||
if not response: return
|
|
||||||
|
|
||||||
uri = response[0]["uri"]
|
|
||||||
if "jdt://" in uri:
|
|
||||||
controller._lsp_java_class_file_contents(uri)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._prompt_goto_request(uri, response[0]["range"])
|
|
||||||
|
|
||||||
def _handle_class_file_contents(self, text: str):
|
|
||||||
event = Event_Factory.create_event("get_active_view")
|
|
||||||
self.emit_to("source_views", event)
|
|
||||||
|
|
||||||
view = event.response
|
|
||||||
file = view.command.exec("new_file")
|
|
||||||
buffer = view.get_buffer()
|
|
||||||
itr = buffer.get_iter_at_mark(buffer.get_insert())
|
|
||||||
lm = GtkSource.LanguageManager.get_default()
|
|
||||||
language = lm.get_language("java")
|
|
||||||
file.ftype = "java"
|
|
||||||
|
|
||||||
buffer.set_language(language)
|
|
||||||
buffer.insert(itr, text, -1)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Module
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Package
|
|
||||||
"""
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
LSP Clients Module
|
|
||||||
"""
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import threading
|
|
||||||
from os import path
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
from gi.repository import GLib
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from ..dto.code.lsp.lsp_messages import get_message_str
|
|
||||||
from ..dto.code.lsp.lsp_message_structs import \
|
|
||||||
LSPResponseTypes, ClientRequest, ClientNotification
|
|
||||||
from .lsp_client_websocket import LSPClientWebsocket
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LSPClient(LSPClientWebsocket):
|
|
||||||
def __init__(self):
|
|
||||||
super(LSPClient, self).__init__()
|
|
||||||
|
|
||||||
self._language: str = ""
|
|
||||||
self._workspace_path: str = ""
|
|
||||||
self._init_params: dict = {}
|
|
||||||
self._init_opts: dict = {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
_USER_HOME = path.expanduser('~')
|
|
||||||
_SCRIPT_PTH = path.dirname( path.realpath(__file__) )
|
|
||||||
_LSP_INIT_CONFIG = f"{_SCRIPT_PTH}/../configs/initialize-params-slim.json"
|
|
||||||
|
|
||||||
with open(_LSP_INIT_CONFIG) as file:
|
|
||||||
data = file.read()
|
|
||||||
self._init_params = json.loads(data)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" )
|
|
||||||
|
|
||||||
|
|
||||||
self._socket = None
|
|
||||||
self._message_id: int = -1
|
|
||||||
self._event_history: dict[int, str] = {}
|
|
||||||
|
|
||||||
self.read_lock = threading.Lock()
|
|
||||||
self.write_lock = threading.Lock()
|
|
||||||
|
|
||||||
|
|
||||||
def set_language(self, language: str):
|
|
||||||
self._language = language
|
|
||||||
|
|
||||||
def set_workspace_path(self, workspace_path: str):
|
|
||||||
self._workspace_path = workspace_path
|
|
||||||
|
|
||||||
def set_init_opts(self, init_opts: dict[str, str]):
|
|
||||||
self._init_opts = init_opts
|
|
||||||
|
|
||||||
def set_socket(self, socket: str):
|
|
||||||
self._socket = socket
|
|
||||||
|
|
||||||
def unset_socket(self):
|
|
||||||
self._socket = None
|
|
||||||
|
|
||||||
def send_notification(self, method: str, params: dict = {}):
|
|
||||||
self._send_message( ClientNotification(method, params) )
|
|
||||||
|
|
||||||
def send_request(self, method: str, params: dict = {}):
|
|
||||||
self._message_id += 1
|
|
||||||
self._event_history[self._message_id] = method
|
|
||||||
self._send_message( ClientRequest(self._message_id, method, params) )
|
|
||||||
|
|
||||||
def get_event_by_id(self, message_id: int) -> str:
|
|
||||||
if not message_id in self._event_history: return
|
|
||||||
return self._event_history[message_id]
|
|
||||||
|
|
||||||
def handle_lsp_response(self, lsp_response: LSPResponseTypes):
|
|
||||||
raise NotImplementedError
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from ..dto.code.lsp.lsp_message_structs import ClientRequest, ClientNotification
|
|
||||||
|
|
||||||
from .lsp_client_events import LSPClientEvents
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LSPClientBase(LSPClientEvents):
|
|
||||||
def _send_message(self, data: ClientRequest or ClientNotification):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def start_client(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def stop_client(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from ..dto.code.lsp.lsp_messages import get_message_obj
|
|
||||||
from ..dto.code.lsp.lsp_messages import didopen_notification
|
|
||||||
from ..dto.code.lsp.lsp_messages import didsave_notification
|
|
||||||
from ..dto.code.lsp.lsp_messages import didclose_notification
|
|
||||||
from ..dto.code.lsp.lsp_messages import didchange_notification
|
|
||||||
from ..dto.code.lsp.lsp_messages import completion_request
|
|
||||||
from ..dto.code.lsp.lsp_messages import definition_request
|
|
||||||
from ..dto.code.lsp.lsp_messages import references_request
|
|
||||||
from ..dto.code.lsp.lsp_messages import symbols_request
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LSPClientEvents:
|
|
||||||
def send_initialize_message(self):
|
|
||||||
folder_name = os.path.basename(self._workspace_path)
|
|
||||||
workspace_uri = f"file://{self._workspace_path}"
|
|
||||||
|
|
||||||
self._init_params["processId"] = None
|
|
||||||
self._init_params["rootPath"] = self._workspace_path
|
|
||||||
self._init_params["rootUri"] = workspace_uri
|
|
||||||
self._init_params["workspaceFolders"] = [
|
|
||||||
{
|
|
||||||
"name": folder_name,
|
|
||||||
"uri": workspace_uri
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
self._init_params["initializationOptions"] = self._init_opts
|
|
||||||
self.send_request("initialize", self._init_params)
|
|
||||||
|
|
||||||
def send_initialized_message(self):
|
|
||||||
self.send_notification("initialized")
|
|
||||||
|
|
||||||
def _lsp_did_open(self, data: dict):
|
|
||||||
method = "textDocument/didOpen"
|
|
||||||
params = didopen_notification["params"]
|
|
||||||
|
|
||||||
params["textDocument"]["uri"] = data["uri"]
|
|
||||||
params["textDocument"]["languageId"] = data["language_id"]
|
|
||||||
params["textDocument"]["text"] = data["text"]
|
|
||||||
|
|
||||||
self.send_notification( method, params )
|
|
||||||
|
|
||||||
def _lsp_did_save(self, data: dict):
|
|
||||||
method = "textDocument/didSave"
|
|
||||||
params = didsave_notification["params"]
|
|
||||||
|
|
||||||
params["textDocument"]["uri"] = data["uri"]
|
|
||||||
params["text"] = data["text"]
|
|
||||||
|
|
||||||
self.send_notification( method, params )
|
|
||||||
|
|
||||||
def _lsp_did_close(self, data: dict):
|
|
||||||
method = "textDocument/didClose"
|
|
||||||
params = didclose_notification["params"]
|
|
||||||
|
|
||||||
params["textDocument"]["uri"] = data["uri"]
|
|
||||||
|
|
||||||
self.send_notification( method, params )
|
|
||||||
|
|
||||||
def _lsp_did_change(self, data: dict):
|
|
||||||
method = "textDocument/didChange"
|
|
||||||
params = didchange_notification["params"]
|
|
||||||
|
|
||||||
params["textDocument"]["uri"] = data["uri"]
|
|
||||||
params["textDocument"]["languageId"] = data["language_id"]
|
|
||||||
params["textDocument"]["version"] = data["version"]
|
|
||||||
|
|
||||||
contentChanges = params["contentChanges"][0]
|
|
||||||
contentChanges["text"] = data["text"]
|
|
||||||
|
|
||||||
self.send_notification( method, params )
|
|
||||||
|
|
||||||
# def _lsp_did_change(self, data: dict):
|
|
||||||
# method = "textDocument/didChange"
|
|
||||||
# params = didchange_notification_range["params"]
|
|
||||||
|
|
||||||
# params["textDocument"]["uri"] = data["uri"]
|
|
||||||
# params["textDocument"]["languageId"] = data["language_id"]
|
|
||||||
# params["textDocument"]["version"] = data["version"]
|
|
||||||
|
|
||||||
# contentChanges = params["contentChanges"][0]
|
|
||||||
# start = contentChanges["range"]["start"]
|
|
||||||
# end = contentChanges["range"]["end"]
|
|
||||||
# contentChanges["text"] = data["text"]
|
|
||||||
# start["line"] = data["line"]
|
|
||||||
# start["character"] = 0
|
|
||||||
# end["line"] = data["line"]
|
|
||||||
# end["character"] = data["column"]
|
|
||||||
|
|
||||||
# self.send_notification( method, params )
|
|
||||||
|
|
||||||
def _lsp_definition(self, data: dict):
|
|
||||||
method = "textDocument/definition"
|
|
||||||
params = definition_request["params"]
|
|
||||||
|
|
||||||
params["textDocument"]["uri"] = data["uri"]
|
|
||||||
params["textDocument"]["languageId"] = data["language_id"]
|
|
||||||
params["textDocument"]["version"] = data["version"]
|
|
||||||
params["position"]["line"] = data["line"]
|
|
||||||
params["position"]["character"] = data["column"]
|
|
||||||
|
|
||||||
self.send_request( method, params )
|
|
||||||
|
|
||||||
def _lsp_completion(self, data: dict):
|
|
||||||
method = "textDocument/completion"
|
|
||||||
params = completion_request["params"]
|
|
||||||
|
|
||||||
params["textDocument"]["uri"] = data["uri"]
|
|
||||||
params["textDocument"]["languageId"] = data["language_id"]
|
|
||||||
params["textDocument"]["version"] = data["version"]
|
|
||||||
params["position"]["line"] = data["line"]
|
|
||||||
params["position"]["character"] = data["column"]
|
|
||||||
|
|
||||||
self.send_request( method, params )
|
|
||||||
|
|
||||||
def _lsp_java_class_file_contents(self, uri: str):
|
|
||||||
method = "java/classFileContents"
|
|
||||||
params = {
|
|
||||||
"uri": uri
|
|
||||||
}
|
|
||||||
|
|
||||||
self.send_request( method, params )
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
from gi.repository import GLib
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
# from libs import websockets
|
|
||||||
from ..dto.code.lsp.lsp_messages import get_message_str, get_message_obj
|
|
||||||
from ..dto.code.lsp.lsp_message_structs import \
|
|
||||||
LSPResponseTypes, ClientRequest, ClientNotification, \
|
|
||||||
LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification
|
|
||||||
|
|
||||||
from .lsp_client_base import LSPClientBase
|
|
||||||
from .websocket_client import WebsocketClient
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LSPClientWebsocket(LSPClientBase):
|
|
||||||
def _send_message(self, data: ClientRequest | ClientNotification):
|
|
||||||
if not data: return
|
|
||||||
|
|
||||||
message_str = get_message_str(data)
|
|
||||||
message_size = len(message_str)
|
|
||||||
message = f"Content-Length: {message_size}\r\n\r\n{message_str}"
|
|
||||||
|
|
||||||
logger.debug(f"Client: {message_str}")
|
|
||||||
self.ws_client.send(message_str)
|
|
||||||
|
|
||||||
def start_client(self):
|
|
||||||
self.ws_client = WebsocketClient()
|
|
||||||
self.ws_client.set_socket(self._socket)
|
|
||||||
self.ws_client.set_callback(self._monitor_lsp_response)
|
|
||||||
self.ws_client.start_client()
|
|
||||||
|
|
||||||
return self.ws_client
|
|
||||||
|
|
||||||
def stop_client(self):
|
|
||||||
if not hasattr(self, "ws_client"): return
|
|
||||||
self.ws_client.close_client()
|
|
||||||
|
|
||||||
def _monitor_lsp_response(self, data: dict | None):
|
|
||||||
if not data: return
|
|
||||||
|
|
||||||
message = get_message_obj(data)
|
|
||||||
keys = message.keys()
|
|
||||||
lsp_response = None
|
|
||||||
|
|
||||||
if "result" in keys:
|
|
||||||
lsp_response = LSPResponseRequest(**get_message_obj(data))
|
|
||||||
|
|
||||||
if "method" in keys:
|
|
||||||
lsp_response = LSPResponseNotification(**get_message_obj(data)) if not "id" in keys else LSPIDResponseNotification( **get_message_obj(data) )
|
|
||||||
|
|
||||||
if not lsp_response: return
|
|
||||||
|
|
||||||
GLib.idle_add(self.handle_lsp_response, lsp_response)
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import json
|
|
||||||
import threading
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from ..libs import websocket
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WebsocketClient:
|
|
||||||
def __init__(self):
|
|
||||||
self.ws = None
|
|
||||||
self._socket = None
|
|
||||||
self._connected = threading.Event()
|
|
||||||
|
|
||||||
|
|
||||||
def set_socket(self, socket: str):
|
|
||||||
self._socket = socket
|
|
||||||
|
|
||||||
def unset_socket(self):
|
|
||||||
self._socket = None
|
|
||||||
|
|
||||||
def send(self, message: str):
|
|
||||||
self.ws.send(message)
|
|
||||||
|
|
||||||
def on_message(self, ws, message: dict):
|
|
||||||
self.respond(message)
|
|
||||||
|
|
||||||
def on_error(self, ws, error: str):
|
|
||||||
logger.debug(f"WS Error: {error}")
|
|
||||||
|
|
||||||
def on_close(self, ws, close_status_code: int, close_msg: str):
|
|
||||||
logger.debug("WS Closed...")
|
|
||||||
|
|
||||||
def on_open(self, ws):
|
|
||||||
self._connected.set()
|
|
||||||
logger.debug("WS opened connection...")
|
|
||||||
|
|
||||||
def wait_for_connection(self, timeout: float = 5.0) -> bool:
|
|
||||||
return self._connected.wait(timeout)
|
|
||||||
|
|
||||||
def set_callback(self, callback: object):
|
|
||||||
self.respond = callback
|
|
||||||
|
|
||||||
def close_client(self):
|
|
||||||
self.ws.close()
|
|
||||||
|
|
||||||
@daemon_threaded
|
|
||||||
def start_client(self):
|
|
||||||
if not self._socket:
|
|
||||||
raise Exception("Socket address isn't set so cannot start WebsocketClient listener...")
|
|
||||||
|
|
||||||
# websocket.enableTrace(True)
|
|
||||||
self.ws = websocket.WebSocketApp(self._socket,
|
|
||||||
on_open = self.on_open,
|
|
||||||
on_message = self.on_message,
|
|
||||||
on_error = self.on_error,
|
|
||||||
on_close = self.on_close)
|
|
||||||
|
|
||||||
self.ws.run_forever(reconnect = 0.5)
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
{
|
|
||||||
"_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize",
|
|
||||||
"processId": "os.getpid()",
|
|
||||||
"clientInfo": {
|
|
||||||
"name": "LSP Manager",
|
|
||||||
"version": "0.0.1"
|
|
||||||
},
|
|
||||||
"locale": "en",
|
|
||||||
"rootPath": "repository_absolute_path",
|
|
||||||
"rootUri": "pathlib.Path(repository_absolute_path).as_uri()",
|
|
||||||
"capabilities": {
|
|
||||||
"textDocument": {
|
|
||||||
"completion": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"contextSupport": true,
|
|
||||||
"completionItem": {
|
|
||||||
"snippetSupport": false,
|
|
||||||
"commitCharactersSupport": true,
|
|
||||||
"documentationFormat": [
|
|
||||||
"markdown",
|
|
||||||
"plaintext"
|
|
||||||
],
|
|
||||||
"deprecatedSupport": true,
|
|
||||||
"preselectSupport": true,
|
|
||||||
"tagSupport": {
|
|
||||||
"valueSet": [
|
|
||||||
1
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"insertReplaceSupport": false,
|
|
||||||
"resolveSupport": {
|
|
||||||
"properties": [
|
|
||||||
"documentation",
|
|
||||||
"detail",
|
|
||||||
"additionalTextEdits"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"insertTextModeSupport": {
|
|
||||||
"valueSet": [
|
|
||||||
1,
|
|
||||||
2
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"labelDetailsSupport": true
|
|
||||||
},
|
|
||||||
"insertTextMode": 2,
|
|
||||||
"completionItemKind": {
|
|
||||||
"valueSet": [
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
4,
|
|
||||||
5,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
8,
|
|
||||||
9,
|
|
||||||
10,
|
|
||||||
11,
|
|
||||||
12,
|
|
||||||
13,
|
|
||||||
14,
|
|
||||||
15,
|
|
||||||
16,
|
|
||||||
17,
|
|
||||||
18,
|
|
||||||
19,
|
|
||||||
20,
|
|
||||||
21,
|
|
||||||
22,
|
|
||||||
23,
|
|
||||||
24,
|
|
||||||
25
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"completionList": {
|
|
||||||
"itemDefaults": [
|
|
||||||
"commitCharacters",
|
|
||||||
"editRange",
|
|
||||||
"insertTextFormat",
|
|
||||||
"insertTextMode"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hover": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"contentFormat": [
|
|
||||||
"markdown",
|
|
||||||
"plaintext"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"signatureHelp": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"signatureInformation": {
|
|
||||||
"documentationFormat": [
|
|
||||||
"markdown",
|
|
||||||
"plaintext"
|
|
||||||
],
|
|
||||||
"parameterInformation": {
|
|
||||||
"labelOffsetSupport": true
|
|
||||||
},
|
|
||||||
"activeParameterSupport": true
|
|
||||||
},
|
|
||||||
"contextSupport": true
|
|
||||||
},
|
|
||||||
"definition": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"linkSupport": true
|
|
||||||
},
|
|
||||||
"references": {
|
|
||||||
"dynamicRegistration": true
|
|
||||||
},
|
|
||||||
"typeDefinition": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"linkSupport": true
|
|
||||||
},
|
|
||||||
"implementation": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"linkSupport": true
|
|
||||||
},
|
|
||||||
"colorProvider": {
|
|
||||||
"dynamicRegistration": true
|
|
||||||
},
|
|
||||||
"declaration": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"linkSupport": true
|
|
||||||
},
|
|
||||||
"callHierarchy": {
|
|
||||||
"dynamicRegistration": true
|
|
||||||
},
|
|
||||||
"inlayHint": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"resolveSupport": {
|
|
||||||
"properties": [
|
|
||||||
"tooltip",
|
|
||||||
"textEdits",
|
|
||||||
"label.tooltip",
|
|
||||||
"label.location",
|
|
||||||
"label.command"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"diagnostic": {
|
|
||||||
"dynamicRegistration": true,
|
|
||||||
"relatedDocumentSupport": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"trace": "verbose",
|
|
||||||
"workspaceFolders": "[\n {\n \"uri\": pathlib.Path(repository_absolute_path).as_uri(),\n \"name\": os.path.basename(repository_absolute_path),\n }\n ]"
|
|
||||||
}
|
|
||||||
@@ -1,365 +0,0 @@
|
|||||||
{
|
|
||||||
"java": {
|
|
||||||
"info": "https://download.eclipse.org/jdtls/",
|
|
||||||
"info-init-options": "https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line",
|
|
||||||
"info-import-build": "https://www.javahotchocolate.com/tutorials/build-path.html",
|
|
||||||
"info-external-class-paths": "https://github.com/eclipse-jdtls/eclipse.jdt.ls/issues/3291",
|
|
||||||
"link": "https://download.eclipse.org/jdtls/milestones/?d",
|
|
||||||
"command": "lsp-ws-proxy --listen 4114 -- jdtls",
|
|
||||||
"alt-command": "lsp-ws-proxy -- jdtls",
|
|
||||||
"alt-command2": "java-language-server",
|
|
||||||
"socket": "ws://127.0.0.1:9999/java",
|
|
||||||
"socket-two": "ws://127.0.0.1:9999/?name=jdtls",
|
|
||||||
"alt-socket": "ws://127.0.0.1:9999/?name=java-language-server",
|
|
||||||
"initialization-options": {
|
|
||||||
"bundles": [
|
|
||||||
"intellicode-core.jar"
|
|
||||||
],
|
|
||||||
"workspaceFolders": [
|
|
||||||
"file://{workspace.folder}"
|
|
||||||
],
|
|
||||||
"extendedClientCapabilities": {
|
|
||||||
"classFileContentsSupport": true,
|
|
||||||
"executeClientCommandSupport": false
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"java": {
|
|
||||||
"autobuild": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"jdt": {
|
|
||||||
"ls": {
|
|
||||||
"javac": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"java": {
|
|
||||||
"home": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2"
|
|
||||||
},
|
|
||||||
"lombokSupport": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"protobufSupport":{
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"androidSupport": {
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"configuration": {
|
|
||||||
"updateBuildConfiguration": "automatic",
|
|
||||||
"maven": {
|
|
||||||
"userSettings": "{user.home}/.config/jdtls/settings.xml",
|
|
||||||
"globalSettings": "{user.home}/.config/jdtls/settings.xml"
|
|
||||||
},
|
|
||||||
"runtimes": [
|
|
||||||
{
|
|
||||||
"name": "JavaSE-17",
|
|
||||||
"path": "/usr/lib/jvm/java-17-openjdk",
|
|
||||||
"javadoc": "https://docs.oracle.com/en/java/javase/17/docs/api/",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "JavaSE-22",
|
|
||||||
"path": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2",
|
|
||||||
"javadoc": "https://docs.oracle.com/en/java/javase/22/docs/api/",
|
|
||||||
"default": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"classPath": [
|
|
||||||
"{user.home}/.config/jdtls/m2/repository/**/*-sources.jar",
|
|
||||||
"lib/**/*-sources.jar"
|
|
||||||
],
|
|
||||||
"docPath": [
|
|
||||||
"{user.home}/.config/jdtls/m2/repository/**/*-javadoc.jar",
|
|
||||||
"lib/**/*-javadoc.jar"
|
|
||||||
],
|
|
||||||
"project": {
|
|
||||||
"encoding": "ignore",
|
|
||||||
"outputPath": "bin",
|
|
||||||
"referencedLibraries": [
|
|
||||||
"{user.home}/.config/jdtls/m2/repository/**/*.jar",
|
|
||||||
"lib/**/*.jar"
|
|
||||||
],
|
|
||||||
"importOnFirstTimeStartup": "automatic",
|
|
||||||
"importHint": true,
|
|
||||||
"resourceFilters": [
|
|
||||||
"node_modules",
|
|
||||||
"\\.git"
|
|
||||||
],
|
|
||||||
"sourcePaths": [
|
|
||||||
"src",
|
|
||||||
"{user.home}/.config/jdtls/m2/repository/**/*.jar"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sources": {
|
|
||||||
"organizeImports": {
|
|
||||||
"starThreshold": 99,
|
|
||||||
"staticStarThreshold": 99
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"imports": {
|
|
||||||
"gradle": {
|
|
||||||
"wrapper": {
|
|
||||||
"checksums": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"import": {
|
|
||||||
"maven": {
|
|
||||||
"enabled": true,
|
|
||||||
"offline": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"disableTestClasspathFlag": false
|
|
||||||
},
|
|
||||||
"gradle": {
|
|
||||||
"enabled": false,
|
|
||||||
"wrapper": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"version": "",
|
|
||||||
"home": "abs(static/gradle-7.3.3)",
|
|
||||||
"java": {
|
|
||||||
"home": "abs(static/launch_jres/17.0.6-linux-x86_64)"
|
|
||||||
},
|
|
||||||
"offline": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"arguments": [],
|
|
||||||
"jvmArguments": [],
|
|
||||||
"user": {
|
|
||||||
"home": ""
|
|
||||||
},
|
|
||||||
"annotationProcessing": {
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"exclusions": [
|
|
||||||
"**/node_modules/**",
|
|
||||||
"**/.metadata/**",
|
|
||||||
"**/archetype-resources/**",
|
|
||||||
"**/META-INF/maven/**"
|
|
||||||
],
|
|
||||||
"generatesMetadataFilesAtProjectRoot": false
|
|
||||||
},
|
|
||||||
"maven": {
|
|
||||||
"downloadSources": true,
|
|
||||||
"updateSnapshots": true
|
|
||||||
},
|
|
||||||
"silentNotification": true,
|
|
||||||
"contentProvider": {
|
|
||||||
"preferred": "fernflower"
|
|
||||||
},
|
|
||||||
"signatureHelp": {
|
|
||||||
"enabled": true,
|
|
||||||
"description": {
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"completion": {
|
|
||||||
"enabled": true,
|
|
||||||
"engine": "ecj",
|
|
||||||
"matchCase": "firstletter",
|
|
||||||
"maxResults": 25,
|
|
||||||
"guessMethodArguments": true,
|
|
||||||
"lazyResolveTextEdit": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"postfix": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"favoriteStaticMembers": [
|
|
||||||
"org.junit.Assert.*",
|
|
||||||
"org.junit.Assume.*",
|
|
||||||
"org.junit.jupiter.api.Assertions.*",
|
|
||||||
"org.junit.jupiter.api.Assumptions.*",
|
|
||||||
"org.junit.jupiter.api.DynamicContainer.*",
|
|
||||||
"org.junit.jupiter.api.DynamicTest.*"
|
|
||||||
],
|
|
||||||
"importOrder": [
|
|
||||||
"#",
|
|
||||||
"java",
|
|
||||||
"javax",
|
|
||||||
"org",
|
|
||||||
"com"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"references": {
|
|
||||||
"includeAccessors": true,
|
|
||||||
"includeDecompiledSources": true
|
|
||||||
},
|
|
||||||
"codeGeneration": {
|
|
||||||
"toString": {
|
|
||||||
"template": "${object.className}{${member.name()}=${member.value}, ${otherMembers}}"
|
|
||||||
},
|
|
||||||
"insertionLocation": "afterCursor",
|
|
||||||
"useBlocks": true
|
|
||||||
},
|
|
||||||
"implementationsCodeLens": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"referencesCodeLens": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"progressReports": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"saveActions": {
|
|
||||||
"organizeImports": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"python": {
|
|
||||||
"info": "https://github.com/python-lsp/python-lsp-server",
|
|
||||||
"command": "lsp-ws-proxy -- pylsp",
|
|
||||||
"alt-command": "pylsp",
|
|
||||||
"alt-command2": "lsp-ws-proxy --listen 4114 -- pylsp",
|
|
||||||
"alt-command3": "pylsp --ws --port 4114",
|
|
||||||
"socket": "ws://127.0.0.1:9999/python",
|
|
||||||
"socket-two": "ws://127.0.0.1:9999/?name=pylsp",
|
|
||||||
"initialization-options": {
|
|
||||||
"pylsp": {
|
|
||||||
"rope": {
|
|
||||||
"ropeFolder": "{user.home}/.config/newton/lsps/ropeproject"
|
|
||||||
},
|
|
||||||
"plugins": {
|
|
||||||
"ruff": {
|
|
||||||
"enabled": true,
|
|
||||||
"extendSelect": ["I"],
|
|
||||||
"lineLength": 80
|
|
||||||
},
|
|
||||||
"pycodestyle": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"pyflakes": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"pylint": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"mccabe": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"pylsp_rope": {
|
|
||||||
"rename": false
|
|
||||||
},
|
|
||||||
"rope_rename": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"rope_autoimport": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"rope_completion": {
|
|
||||||
"enabled": false,
|
|
||||||
"eager": false
|
|
||||||
},
|
|
||||||
"jedi_rename": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"jedi_completion": {
|
|
||||||
"enabled": true,
|
|
||||||
"include_class_objects": true,
|
|
||||||
"include_function_objects": true,
|
|
||||||
"fuzzy": false
|
|
||||||
},
|
|
||||||
"jedi": {
|
|
||||||
"root_dir": "file://{workspace.folder}",
|
|
||||||
"extra_paths": [
|
|
||||||
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"python - jedi-language-server": {
|
|
||||||
"hidden": true,
|
|
||||||
"info": "https://pypi.org/project/jedi-language-server/",
|
|
||||||
"command": "jedi-language-server",
|
|
||||||
"alt-command": "lsp-ws-proxy --listen 3030 -- jedi-language-server",
|
|
||||||
"socket": "ws://127.0.0.1:9999/python",
|
|
||||||
"socket-two": "ws://127.0.0.1:9999/?name=jedi-language-server",
|
|
||||||
"initialization-options": {
|
|
||||||
"jediSettings": {
|
|
||||||
"autoImportModules": [],
|
|
||||||
"caseInsensitiveCompletion": true,
|
|
||||||
"debug": false
|
|
||||||
},
|
|
||||||
"completion": {
|
|
||||||
"disableSnippets": false,
|
|
||||||
"resolveEagerly": false,
|
|
||||||
"ignorePatterns": []
|
|
||||||
},
|
|
||||||
"markupKindPreferred": "markdown",
|
|
||||||
"workspace": {
|
|
||||||
"extraPaths": [
|
|
||||||
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
|
|
||||||
],
|
|
||||||
"environmentPath": "{user.home}/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/python",
|
|
||||||
"symbols": {
|
|
||||||
"ignoreFolders": [
|
|
||||||
".nox",
|
|
||||||
".tox",
|
|
||||||
".venv",
|
|
||||||
"__pycache__",
|
|
||||||
"venv"
|
|
||||||
],
|
|
||||||
"maxSymbols": 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cpp": {
|
|
||||||
"info": "https://clangd.llvm.org/",
|
|
||||||
"command": "lsp-ws-proxy -- clangd",
|
|
||||||
"alt-command": "clangd",
|
|
||||||
"socket": "ws://127.0.0.1:9999/cpp",
|
|
||||||
"socket-two": "ws://127.0.0.1:9999/?name=clangd",
|
|
||||||
"initialization-options": {}
|
|
||||||
},
|
|
||||||
"c": {
|
|
||||||
"hidden": true,
|
|
||||||
"info": "https://clangd.llvm.org/",
|
|
||||||
"command": "lsp-ws-proxy -- clangd",
|
|
||||||
"alt-command": "clangd",
|
|
||||||
"socket": "ws://127.0.0.1:9999/c",
|
|
||||||
"socket-two": "ws://127.0.0.1:9999/?name=clangd",
|
|
||||||
"initialization-options": {}
|
|
||||||
},
|
|
||||||
"go": {
|
|
||||||
"info": "https://pkg.go.dev/golang.org/x/tools/gopls#section-readme",
|
|
||||||
"command": "lsp-ws-proxy -- gopls",
|
|
||||||
"alt-command": "gopls",
|
|
||||||
"socket": "ws://127.0.0.1:9999/go",
|
|
||||||
"socket-two": "ws://127.0.0.1:9999/?name=gopls",
|
|
||||||
"initialization-options": {}
|
|
||||||
},
|
|
||||||
"typescript": {
|
|
||||||
"info": "https://github.com/typescript-language-server/typescript-language-server",
|
|
||||||
"command": "lsp-ws-proxy -- typescript-language-server",
|
|
||||||
"alt-command": "typescript-language-server --stdio",
|
|
||||||
"socket": "ws://127.0.0.1:9999/typescript",
|
|
||||||
"socket-two": "ws://127.0.0.1:9999/?name=ts",
|
|
||||||
"initialization-options": {}
|
|
||||||
},
|
|
||||||
"sh": {
|
|
||||||
"info": "",
|
|
||||||
"command": "",
|
|
||||||
"alt-command": "",
|
|
||||||
"socket": "ws://127.0.0.1:9999/bash",
|
|
||||||
"socket-two": "ws://127.0.0.1:9999/?name=shell",
|
|
||||||
"initialization-options": {}
|
|
||||||
},
|
|
||||||
"lua": {
|
|
||||||
"info": "https://github.com/LuaLS/lua-language-server",
|
|
||||||
"command": "lsp-ws-proxy -- lua-language-server",
|
|
||||||
"alt-command": "lua-language-server",
|
|
||||||
"socket": "ws://127.0.0.1:9999/lua",
|
|
||||||
"socket-two": "ws://127.0.0.1:9999/?name=lua",
|
|
||||||
"initialization-options": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
"""
|
|
||||||
Libs Code DTO(s) Events Package
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .lsp_event import LspEvent
|
|
||||||
|
|
||||||
from .register_lsp_client_event import RegisterLspClientEvent
|
|
||||||
from .unregister_lsp_client_event import UnregisterLspClientEvent
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.dto.code.events import CodeEvent
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class LspEvent(CodeEvent):
|
|
||||||
...
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from ....response_handlers.base_handler import BaseHandler
|
|
||||||
|
|
||||||
from .lsp_event import LspEvent
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RegisterLspClientEvent(LspEvent):
|
|
||||||
lang_id: str = ""
|
|
||||||
lang_config: str = "{}"
|
|
||||||
handler: BaseHandler = None
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from .lsp_event import LspEvent
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class UnregisterLspClientEvent(LspEvent):
|
|
||||||
lang_id: str = ""
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import json
|
|
||||||
import enum
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from .lsp_structs import TextDocumentItem
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MessageEncoder(json.JSONEncoder):
|
|
||||||
"""
|
|
||||||
Encodes an object in JSON
|
|
||||||
"""
|
|
||||||
|
|
||||||
def default(self, o): # pylint: disable=E0202
|
|
||||||
return o.__dict__
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ClientRequest(object):
|
|
||||||
def __init__(self, id: int, method: str, params: dict):
|
|
||||||
"""
|
|
||||||
Constructs a new Client Request instance.
|
|
||||||
|
|
||||||
:param int id: Message id to track instance.
|
|
||||||
:param str method: The type of lsp request being made.
|
|
||||||
:param dict params: The arguments of the given method.
|
|
||||||
"""
|
|
||||||
self.jsonrpc = "2.0"
|
|
||||||
self.id = id
|
|
||||||
self.method = method
|
|
||||||
self.params = params
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ClientNotification(object):
|
|
||||||
def __init__(self, method: str, params: dict):
|
|
||||||
"""
|
|
||||||
Constructs a new Client Notification instance.
|
|
||||||
|
|
||||||
:param str method: The type of lsp notification being made.
|
|
||||||
:param dict params: The arguments of the given method.
|
|
||||||
"""
|
|
||||||
self.jsonrpc = "2.0"
|
|
||||||
self.method = method
|
|
||||||
self.params = params
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class LSPResponseRequest(object):
|
|
||||||
"""
|
|
||||||
Constructs a new LSP Response Request instance.
|
|
||||||
|
|
||||||
:param id result: The id of the given message.
|
|
||||||
:param dict result: The arguments of the given method.
|
|
||||||
"""
|
|
||||||
jsonrpc: str
|
|
||||||
id: int
|
|
||||||
result: dict
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class LSPResponseNotification(object):
|
|
||||||
"""
|
|
||||||
Constructs a new LSP Response Notification instance.
|
|
||||||
|
|
||||||
:param str method: The type of lsp notification being made.
|
|
||||||
:params dict result: The arguments of the given method.
|
|
||||||
"""
|
|
||||||
jsonrpc: str
|
|
||||||
method: str
|
|
||||||
params: dict
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class LSPIDResponseNotification(object):
|
|
||||||
"""
|
|
||||||
Constructs a new LSP Response Notification instance.
|
|
||||||
|
|
||||||
:param str method: The type of lsp notification being made.
|
|
||||||
:params dict result: The arguments of the given method.
|
|
||||||
"""
|
|
||||||
jsonrpc: str
|
|
||||||
id: int
|
|
||||||
method: str
|
|
||||||
params: dict
|
|
||||||
|
|
||||||
|
|
||||||
class MessageTypes(ClientRequest, ClientNotification, LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification):
|
|
||||||
...
|
|
||||||
|
|
||||||
class ClientMessageTypes(ClientRequest, ClientNotification):
|
|
||||||
...
|
|
||||||
|
|
||||||
class LSPResponseTypes(LSPResponseRequest, LSPResponseNotification):
|
|
||||||
...
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from .lsp_message_structs import MessageEncoder
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
LEN_HEADER = "Content-Length: "
|
|
||||||
TYPE_HEADER = "Content-Type: "
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_message_str(data: dict) -> str:
|
|
||||||
return json.dumps(data, separators = (',', ':'), indent = 4, cls = MessageEncoder)
|
|
||||||
|
|
||||||
def get_message_obj(data: str):
|
|
||||||
return json.loads(data)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Request type formatting
|
|
||||||
# https://github.com/microsoft/multilspy/blob/main/src/multilspy/language_server.py#L417
|
|
||||||
content_part = {
|
|
||||||
"method": "textDocument/definition",
|
|
||||||
"params": {
|
|
||||||
"textDocument": {
|
|
||||||
"uri": "file://",
|
|
||||||
"languageId": "python",
|
|
||||||
"version": 1,
|
|
||||||
"text": ""
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"line": 5,
|
|
||||||
"character": 12,
|
|
||||||
"offset": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
didopen_notification = {
|
|
||||||
"method": "textDocument/didOpen",
|
|
||||||
"params": {
|
|
||||||
"textDocument": {
|
|
||||||
"uri": "file://",
|
|
||||||
"languageId": "python",
|
|
||||||
"version": 1,
|
|
||||||
"text": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
didsave_notification = {
|
|
||||||
"method": "textDocument/didSave",
|
|
||||||
"params": {
|
|
||||||
"textDocument": {
|
|
||||||
"uri": "file://"
|
|
||||||
},
|
|
||||||
"text": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
didclose_notification = {
|
|
||||||
"method": "textDocument/didClose",
|
|
||||||
"params": {
|
|
||||||
"textDocument": {
|
|
||||||
"uri": "file://"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
didchange_notification = {
|
|
||||||
"method": "textDocument/didChange",
|
|
||||||
"params": {
|
|
||||||
"textDocument": {
|
|
||||||
"uri": "file://",
|
|
||||||
"languageId": "python",
|
|
||||||
"version": 1,
|
|
||||||
},
|
|
||||||
"contentChanges": [
|
|
||||||
{
|
|
||||||
"text": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
didchange_notification_range = {
|
|
||||||
"method": "textDocument/didChange",
|
|
||||||
"params": {
|
|
||||||
"textDocument": {
|
|
||||||
"uri": "file://",
|
|
||||||
"languageId": "python",
|
|
||||||
"version": 1,
|
|
||||||
"text": ""
|
|
||||||
},
|
|
||||||
"contentChanges": [
|
|
||||||
{
|
|
||||||
"range": {
|
|
||||||
"start": {
|
|
||||||
"line": 1,
|
|
||||||
"character": 1,
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 1,
|
|
||||||
"character": 1,
|
|
||||||
},
|
|
||||||
"rangeLength": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# CompletionTriggerKind = 1 | 2 | 3;
|
|
||||||
# export const Invoked: 1 = 1;
|
|
||||||
# export const TriggerCharacter: 2 = 2;
|
|
||||||
# export const TriggerForIncompleteCompletions: 3 = 3;
|
|
||||||
completion_request = {
|
|
||||||
"method": "textDocument/completion",
|
|
||||||
"params": {
|
|
||||||
"textDocument": {
|
|
||||||
"uri": "file://",
|
|
||||||
"languageId": "python",
|
|
||||||
"version": 1,
|
|
||||||
"text": ""
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"line": 5,
|
|
||||||
"character": 12,
|
|
||||||
"offset": 0
|
|
||||||
},
|
|
||||||
"contet": {
|
|
||||||
"triggerKind": 3,
|
|
||||||
"triggerCharacter": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
definition_request = {
|
|
||||||
"method": "textDocument/definition",
|
|
||||||
"params": {
|
|
||||||
"textDocument": {
|
|
||||||
"uri": "file://",
|
|
||||||
"languageId": "python",
|
|
||||||
"version": 1,
|
|
||||||
"text": ""
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"line": 5,
|
|
||||||
"character": 12,
|
|
||||||
"offset": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
references_request = {
|
|
||||||
"method": "textDocument/references",
|
|
||||||
"params": {
|
|
||||||
"context": {
|
|
||||||
"includeDeclaration": False
|
|
||||||
},
|
|
||||||
"textDocument": {
|
|
||||||
"uri": "file://",
|
|
||||||
"languageId": "python",
|
|
||||||
"version": 1,
|
|
||||||
"text": ""
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"line": 30,
|
|
||||||
"character": 13,
|
|
||||||
"offset": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
symbols_request = {
|
|
||||||
"method": "textDocument/documentSymbol",
|
|
||||||
"params": {
|
|
||||||
"textDocument": {
|
|
||||||
"uri": "file://",
|
|
||||||
"languageId": "python",
|
|
||||||
"version": 1,
|
|
||||||
"text": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,571 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import enum
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def to_type(o, new_type):
|
|
||||||
'''
|
|
||||||
Helper funciton that receives an object or a dict and convert it to a new
|
|
||||||
given type.
|
|
||||||
|
|
||||||
:param object|dict o: The object to convert
|
|
||||||
:param Type new_type: The type to convert to.
|
|
||||||
'''
|
|
||||||
|
|
||||||
return o if new_type == type(o) else new_type(**o)
|
|
||||||
|
|
||||||
|
|
||||||
class Position(object):
|
|
||||||
def __init__(self, line, character):
|
|
||||||
"""
|
|
||||||
Constructs a new Position instance.
|
|
||||||
|
|
||||||
:param int line: Line position in a document (zero-based).
|
|
||||||
:param int character: Character offset on a line in a document
|
|
||||||
(zero-based).
|
|
||||||
"""
|
|
||||||
self.line = line
|
|
||||||
self.character = character
|
|
||||||
|
|
||||||
|
|
||||||
class Range(object):
|
|
||||||
def __init__(self, start, end):
|
|
||||||
"""
|
|
||||||
Constructs a new Range instance.
|
|
||||||
|
|
||||||
:param Position start: The range's start position.
|
|
||||||
:param Position end: The range's end position.
|
|
||||||
"""
|
|
||||||
self.start = to_type(start, Position)
|
|
||||||
self.end = to_type(end, Position)
|
|
||||||
|
|
||||||
|
|
||||||
class Location(object):
|
|
||||||
"""
|
|
||||||
Represents a location inside a resource, such as a line inside a text file.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, uri, range):
|
|
||||||
"""
|
|
||||||
Constructs a new Location instance.
|
|
||||||
|
|
||||||
:param str uri: Resource file.
|
|
||||||
:param Range range: The range inside the file
|
|
||||||
"""
|
|
||||||
self.uri = uri
|
|
||||||
self.range = to_type(range, Range)
|
|
||||||
|
|
||||||
|
|
||||||
class LocationLink(object):
|
|
||||||
"""
|
|
||||||
Represents a link between a source and a target location.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, originSelectionRange, targetUri, targetRange, targetSelectionRange):
|
|
||||||
"""
|
|
||||||
Constructs a new LocationLink instance.
|
|
||||||
|
|
||||||
:param Range originSelectionRange: Span of the origin of this link.
|
|
||||||
Used as the underlined span for mouse interaction. Defaults to the word range at the mouse position.
|
|
||||||
:param str targetUri: The target resource identifier of this link.
|
|
||||||
:param Range targetRange: The full target range of this link. If the target for example is a symbol then target
|
|
||||||
range is the range enclosing this symbol not including leading/trailing whitespace but everything else
|
|
||||||
like comments. This information is typically used to highlight the range in the editor.
|
|
||||||
:param Range targetSelectionRange: The range that should be selected and revealed when this link is being followed,
|
|
||||||
e.g the name of a function. Must be contained by the the `targetRange`. See also `DocumentSymbol#range`
|
|
||||||
"""
|
|
||||||
self.originSelectionRange = to_type(originSelectionRange, Range)
|
|
||||||
self.targetUri = targetUri
|
|
||||||
self.targetRange = to_type(targetRange, Range)
|
|
||||||
self.targetSelectionRange = to_type(targetSelectionRange, Range)
|
|
||||||
|
|
||||||
|
|
||||||
class Diagnostic(object):
|
|
||||||
def __init__(self, range, severity, code, source, message, relatedInformation):
|
|
||||||
"""
|
|
||||||
Constructs a new Diagnostic instance.
|
|
||||||
:param Range range: The range at which the message applies.Resource file.
|
|
||||||
:param int severity: The diagnostic's severity. Can be omitted. If omitted it is up to the
|
|
||||||
client to interpret diagnostics as error, warning, info or hint.
|
|
||||||
:param str code: The diagnostic's code, which might appear in the user interface.
|
|
||||||
:param str source: A human-readable string describing the source of this
|
|
||||||
diagnostic, e.g. 'typescript' or 'super lint'.
|
|
||||||
:param str message: The diagnostic's message.
|
|
||||||
:param list relatedInformation: An array of related diagnostic information, e.g. when symbol-names within
|
|
||||||
a scope collide all definitions can be marked via this property.
|
|
||||||
"""
|
|
||||||
self.range = range
|
|
||||||
self.severity = severity
|
|
||||||
self.code = code
|
|
||||||
self.source = source
|
|
||||||
self.message = message
|
|
||||||
self.relatedInformation = relatedInformation
|
|
||||||
|
|
||||||
|
|
||||||
class DiagnosticSeverity(object):
|
|
||||||
Error = 1
|
|
||||||
Warning = 2 # TODO: warning is known in python
|
|
||||||
Information = 3
|
|
||||||
Hint = 4
|
|
||||||
|
|
||||||
|
|
||||||
class DiagnosticRelatedInformation(object):
|
|
||||||
def __init__(self, location, message):
|
|
||||||
"""
|
|
||||||
Constructs a new Diagnostic instance.
|
|
||||||
:param Location location: The location of this related diagnostic information.
|
|
||||||
:param str message: The message of this related diagnostic information.
|
|
||||||
"""
|
|
||||||
self.location = location
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
|
|
||||||
class Command(object):
|
|
||||||
def __init__(self, title, command, arguments):
|
|
||||||
"""
|
|
||||||
Constructs a new Diagnostic instance.
|
|
||||||
:param str title: Title of the command, like `save`.
|
|
||||||
:param str command: The identifier of the actual command handler.
|
|
||||||
:param list argusments: Arguments that the command handler should be invoked with.
|
|
||||||
"""
|
|
||||||
self.title = title
|
|
||||||
self.command = command
|
|
||||||
self.arguments = arguments
|
|
||||||
|
|
||||||
|
|
||||||
class TextDocumentItem(object):
|
|
||||||
"""
|
|
||||||
An item to transfer a text document from the client to the server.
|
|
||||||
"""
|
|
||||||
def __init__(self, uri, languageId, version, text):
|
|
||||||
"""
|
|
||||||
Constructs a new Diagnostic instance.
|
|
||||||
|
|
||||||
:param DocumentUri uri: uri file path.
|
|
||||||
:param str languageId: The identifier of the actual command handler.
|
|
||||||
:param int version: Arguments that the command handler should be invoked with.
|
|
||||||
:param str text: Arguments that the command handler should be invoked with.
|
|
||||||
"""
|
|
||||||
self.uri = uri
|
|
||||||
self.languageId = languageId
|
|
||||||
self.version = version
|
|
||||||
self.text = text
|
|
||||||
|
|
||||||
|
|
||||||
class TextDocumentIdentifier(object):
|
|
||||||
"""
|
|
||||||
Text documents are identified using a URI. On the protocol level, URIs are passed as strings.
|
|
||||||
"""
|
|
||||||
def __init__(self, uri):
|
|
||||||
"""
|
|
||||||
Constructs a new TextDocumentIdentifier instance.
|
|
||||||
|
|
||||||
:param DocumentUri uri: The text document's URI.
|
|
||||||
"""
|
|
||||||
self.uri = uri
|
|
||||||
|
|
||||||
|
|
||||||
class VersionedTextDocumentIdentifier(TextDocumentIdentifier):
|
|
||||||
"""
|
|
||||||
An identifier to denote a specific version of a text document.
|
|
||||||
"""
|
|
||||||
def __init__(self, uri, version):
|
|
||||||
"""
|
|
||||||
Constructs a new TextDocumentIdentifier instance.
|
|
||||||
|
|
||||||
:param DocumentUri uri: The text document's URI.
|
|
||||||
:param int version: The version number of this document. If a versioned
|
|
||||||
text document identifier is sent from the server to the client and
|
|
||||||
the file is not open in the editor (the server has not received an
|
|
||||||
open notification before) the server can send `null` to indicate
|
|
||||||
that the version is known and the content on disk is the truth (as
|
|
||||||
speced with document content ownership).
|
|
||||||
The version number of a document will increase after each change, including
|
|
||||||
undo/redo. The number doesn't need to be consecutive.
|
|
||||||
"""
|
|
||||||
super(VersionedTextDocumentIdentifier, self).__init__(uri)
|
|
||||||
self.version = version
|
|
||||||
|
|
||||||
|
|
||||||
class TextDocumentContentChangeEvent(object):
|
|
||||||
"""
|
|
||||||
An event describing a change to a text document. If range and rangeLength are omitted
|
|
||||||
the new text is considered to be the full content of the document.
|
|
||||||
"""
|
|
||||||
def __init__(self, range, rangeLength, text):
|
|
||||||
"""
|
|
||||||
Constructs a new TextDocumentContentChangeEvent instance.
|
|
||||||
|
|
||||||
:param Range range: The range of the document that changed.
|
|
||||||
:param int rangeLength: The length of the range that got replaced.
|
|
||||||
:param str text: The new text of the range/document.
|
|
||||||
"""
|
|
||||||
self.range = range
|
|
||||||
self.rangeLength = rangeLength
|
|
||||||
self.text = text
|
|
||||||
|
|
||||||
|
|
||||||
class TextDocumentPositionParams(object):
|
|
||||||
"""
|
|
||||||
A parameter literal used in requests to pass a text document and a position inside that document.
|
|
||||||
"""
|
|
||||||
def __init__(self, textDocument, position):
|
|
||||||
"""
|
|
||||||
Constructs a new TextDocumentPositionParams instance.
|
|
||||||
|
|
||||||
:param TextDocumentIdentifier textDocument: The text document.
|
|
||||||
:param Position position: The position inside the text document.
|
|
||||||
"""
|
|
||||||
self.textDocument = textDocument
|
|
||||||
self.position = position
|
|
||||||
|
|
||||||
|
|
||||||
class LANGUAGE_IDENTIFIER(object):
|
|
||||||
BAT = "bat"
|
|
||||||
BIBTEX = "bibtex"
|
|
||||||
CLOJURE = "clojure"
|
|
||||||
COFFESCRIPT = "coffeescript"
|
|
||||||
C = "c"
|
|
||||||
CPP = "cpp"
|
|
||||||
CSHARP = "csharp"
|
|
||||||
CSS = "css"
|
|
||||||
DIFF = "diff"
|
|
||||||
DOCKERFILE = "dockerfile"
|
|
||||||
FSHARP = "fsharp"
|
|
||||||
GIT_COMMIT = "git-commit"
|
|
||||||
GIT_REBASE = "git-rebase"
|
|
||||||
GO = "go"
|
|
||||||
GROOVY = "groovy"
|
|
||||||
HANDLEBARS = "handlebars"
|
|
||||||
HTML = "html"
|
|
||||||
INI = "ini"
|
|
||||||
JAVA = "java"
|
|
||||||
JAVASCRIPT = "javascript"
|
|
||||||
JSON = "json"
|
|
||||||
LATEX = "latex"
|
|
||||||
LESS = "less"
|
|
||||||
LUA = "lua"
|
|
||||||
MAKEFILE = "makefile"
|
|
||||||
MARKDOWN = "markdown"
|
|
||||||
OBJECTIVE_C = "objective-c"
|
|
||||||
OBJECTIVE_CPP = "objective-cpp"
|
|
||||||
Perl = "perl"
|
|
||||||
PHP = "php"
|
|
||||||
POWERSHELL = "powershell"
|
|
||||||
PUG = "jade"
|
|
||||||
PYTHON = "python"
|
|
||||||
R = "r"
|
|
||||||
RAZOR = "razor"
|
|
||||||
RUBY = "ruby"
|
|
||||||
RUST = "rust"
|
|
||||||
SASS = "sass"
|
|
||||||
SCSS = "scss"
|
|
||||||
ShaderLab = "shaderlab"
|
|
||||||
SHELL_SCRIPT = "shellscript"
|
|
||||||
SQL = "sql"
|
|
||||||
SWIFT = "swift"
|
|
||||||
TYPE_SCRIPT = "typescript"
|
|
||||||
TEX = "tex"
|
|
||||||
VB = "vb"
|
|
||||||
XML = "xml"
|
|
||||||
XSL = "xsl"
|
|
||||||
YAML = "yaml"
|
|
||||||
|
|
||||||
|
|
||||||
class SymbolKind(enum.Enum):
|
|
||||||
File = 1
|
|
||||||
Module = 2
|
|
||||||
Namespace = 3
|
|
||||||
Package = 4
|
|
||||||
Class = 5
|
|
||||||
Method = 6
|
|
||||||
Property = 7
|
|
||||||
Field = 8
|
|
||||||
Constructor = 9
|
|
||||||
Enum = 10
|
|
||||||
Interface = 11
|
|
||||||
Function = 12
|
|
||||||
Variable = 13
|
|
||||||
Constant = 14
|
|
||||||
String = 15
|
|
||||||
Number = 16
|
|
||||||
Boolean = 17
|
|
||||||
Array = 18
|
|
||||||
Object = 19
|
|
||||||
Key = 20
|
|
||||||
Null = 21
|
|
||||||
EnumMember = 22
|
|
||||||
Struct = 23
|
|
||||||
Event = 24
|
|
||||||
Operator = 25
|
|
||||||
TypeParameter = 26
|
|
||||||
|
|
||||||
|
|
||||||
class SymbolInformation(object):
|
|
||||||
"""
|
|
||||||
Represents information about programming constructs like variables, classes, interfaces etc.
|
|
||||||
"""
|
|
||||||
def __init__(self, name, kind, location, containerName = None, deprecated = False):
|
|
||||||
"""
|
|
||||||
Constructs a new SymbolInformation instance.
|
|
||||||
|
|
||||||
:param str name: The name of this symbol.
|
|
||||||
:param int kind: The kind of this symbol.
|
|
||||||
:param bool Location: The location of this symbol. The location's range is used by a tool
|
|
||||||
to reveal the location in the editor. If the symbol is selected in the
|
|
||||||
tool the range's start information is used to position the cursor. So
|
|
||||||
the range usually spans more then the actual symbol's name and does
|
|
||||||
normally include things like visibility modifiers.
|
|
||||||
|
|
||||||
The range doesn't have to denote a node range in the sense of a abstract
|
|
||||||
syntax tree. It can therefore not be used to re-construct a hierarchy of
|
|
||||||
the symbols.
|
|
||||||
:param str containerName: The name of the symbol containing this symbol. This information is for
|
|
||||||
user interface purposes (e.g. to render a qualifier in the user interface
|
|
||||||
if necessary). It can't be used to re-infer a hierarchy for the document
|
|
||||||
symbols.
|
|
||||||
:param bool deprecated: Indicates if this symbol is deprecated.
|
|
||||||
"""
|
|
||||||
self.name = name
|
|
||||||
self.kind = SymbolKind(kind)
|
|
||||||
self.deprecated = deprecated
|
|
||||||
self.location = to_type(location, Location)
|
|
||||||
self.containerName = containerName
|
|
||||||
|
|
||||||
|
|
||||||
class ParameterInformation(object):
|
|
||||||
"""
|
|
||||||
Represents a parameter of a callable-signature. A parameter can
|
|
||||||
have a label and a doc-comment.
|
|
||||||
"""
|
|
||||||
def __init__(self, label, documentation = ""):
|
|
||||||
"""
|
|
||||||
Constructs a new ParameterInformation instance.
|
|
||||||
|
|
||||||
:param str label: The label of this parameter. Will be shown in the UI.
|
|
||||||
:param str documentation: The human-readable doc-comment of this parameter. Will be shown in the UI but can be omitted.
|
|
||||||
"""
|
|
||||||
self.label = label
|
|
||||||
self.documentation = documentation
|
|
||||||
|
|
||||||
|
|
||||||
class SignatureInformation(object):
|
|
||||||
"""
|
|
||||||
Represents the signature of something callable. A signature
|
|
||||||
can have a label, like a function-name, a doc-comment, and
|
|
||||||
a set of parameters.
|
|
||||||
"""
|
|
||||||
def __init__(self, label, documentation = "", parameters = []):
|
|
||||||
"""
|
|
||||||
Constructs a new SignatureInformation instance.
|
|
||||||
|
|
||||||
:param str label: The label of this signature. Will be shown in the UI.
|
|
||||||
:param str documentation: The human-readable doc-comment of this signature. Will be shown in the UI but can be omitted.
|
|
||||||
:param ParameterInformation[] parameters: The parameters of this signature.
|
|
||||||
"""
|
|
||||||
self.label = label
|
|
||||||
self.documentation = documentation
|
|
||||||
self.parameters = [to_type(parameter, ParameterInformation) for parameter in parameters]
|
|
||||||
|
|
||||||
|
|
||||||
class SignatureHelp(object):
|
|
||||||
"""
|
|
||||||
Signature help represents the signature of something
|
|
||||||
callable. There can be multiple signature but only one
|
|
||||||
active and only one active parameter.
|
|
||||||
"""
|
|
||||||
def __init__(self, signatures, activeSignature = 0, activeParameter = 0):
|
|
||||||
"""
|
|
||||||
Constructs a new SignatureHelp instance.
|
|
||||||
|
|
||||||
:param SignatureInformation[] signatures: One or more signatures.
|
|
||||||
:param int activeSignature:
|
|
||||||
:param int activeParameter:
|
|
||||||
"""
|
|
||||||
self.signatures = [to_type(signature, SignatureInformation) for signature in signatures]
|
|
||||||
self.activeSignature = activeSignature
|
|
||||||
self.activeParameter = activeParameter
|
|
||||||
|
|
||||||
|
|
||||||
class CompletionTriggerKind(object):
|
|
||||||
Invoked = 1
|
|
||||||
TriggerCharacter = 2
|
|
||||||
TriggerForIncompleteCompletions = 3
|
|
||||||
|
|
||||||
|
|
||||||
class CompletionContext(object):
|
|
||||||
"""
|
|
||||||
Contains additional information about the context in which a completion request is triggered.
|
|
||||||
"""
|
|
||||||
def __init__(self, triggerKind, triggerCharacter = None):
|
|
||||||
"""
|
|
||||||
Constructs a new CompletionContext instance.
|
|
||||||
|
|
||||||
:param CompletionTriggerKind triggerKind: How the completion was triggered.
|
|
||||||
:param str triggerCharacter: The trigger character (a single character) that has trigger code complete.
|
|
||||||
Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`
|
|
||||||
"""
|
|
||||||
self.triggerKind = triggerKind
|
|
||||||
if triggerCharacter:
|
|
||||||
self.triggerCharacter = triggerCharacter
|
|
||||||
|
|
||||||
|
|
||||||
class TextEdit(object):
|
|
||||||
"""
|
|
||||||
A textual edit applicable to a text document.
|
|
||||||
"""
|
|
||||||
def __init__(self, range, newText):
|
|
||||||
"""
|
|
||||||
:param Range range: The range of the text document to be manipulated. To insert
|
|
||||||
text into a document create a range where start === end.
|
|
||||||
:param str newText: The string to be inserted. For delete operations use an empty string.
|
|
||||||
"""
|
|
||||||
self.range = range
|
|
||||||
self.newText = newText
|
|
||||||
|
|
||||||
|
|
||||||
class InsertTextFormat(object):
|
|
||||||
PlainText = 1
|
|
||||||
Snippet = 2
|
|
||||||
|
|
||||||
|
|
||||||
class CompletionItem(object):
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
def __init__(self, label, \
|
|
||||||
kind = None, \
|
|
||||||
detail = None, \
|
|
||||||
documentation = None, \
|
|
||||||
deprecated = None, \
|
|
||||||
preselect = None, \
|
|
||||||
sortText = None, \
|
|
||||||
filterText = None, \
|
|
||||||
insertText = None, \
|
|
||||||
insertTextFormat = None, \
|
|
||||||
textEdit = None, \
|
|
||||||
additionalTextEdits = None, \
|
|
||||||
commitCharacters = None, \
|
|
||||||
command = None, \
|
|
||||||
data = None, \
|
|
||||||
score = 0.0
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
:param str label: The label of this completion item. By default also the text that is inserted when selecting
|
|
||||||
this completion.
|
|
||||||
:param int kind: The kind of this completion item. Based of the kind an icon is chosen by the editor.
|
|
||||||
:param str detail: A human-readable string with additional information about this item, like type or symbol information.
|
|
||||||
:param tr ocumentation: A human-readable string that represents a doc-comment.
|
|
||||||
:param bool deprecated: Indicates if this item is deprecated.
|
|
||||||
:param bool preselect: Select this item when showing. Note: that only one completion item can be selected and that the
|
|
||||||
tool / client decides which item that is. The rule is that the first item of those that match best is selected.
|
|
||||||
:param str sortText: A string that should be used when comparing this item with other items. When `falsy` the label is used.
|
|
||||||
:param str filterText: A string that should be used when filtering a set of completion items. When `falsy` the label is used.
|
|
||||||
:param str insertText: A string that should be inserted into a document when selecting this completion. When `falsy` the label is used.
|
|
||||||
The `insertText` is subject to interpretation by the client side. Some tools might not take the string literally. For example
|
|
||||||
VS Code when code complete is requested in this example `con<cursor position>` and a completion item with an `insertText` of `console` is provided it
|
|
||||||
will only insert `sole`. Therefore it is recommended to use `textEdit` instead since it avoids additional client side interpretation.
|
|
||||||
@deprecated Use textEdit instead.
|
|
||||||
:param InsertTextFormat insertTextFormat: The format of the insert text. The format applies to both the `insertText` property
|
|
||||||
and the `newText` property of a provided `textEdit`.
|
|
||||||
:param TextEdit textEdit: An edit which is applied to a document when selecting this completion. When an edit is provided the value of `insertText` is ignored.
|
|
||||||
Note:* The range of the edit must be a single line range and it must contain the position at which completion
|
|
||||||
has been requested.
|
|
||||||
:param TextEdit additionalTextEdits: An optional array of additional text edits that are applied when selecting this completion.
|
|
||||||
Edits must not overlap (including the same insert position) with the main edit nor with themselves.
|
|
||||||
Additional text edits should be used to change text unrelated to the current cursor position
|
|
||||||
(for example adding an import statement at the top of the file if the completion item will
|
|
||||||
insert an unqualified type).
|
|
||||||
:param str commitCharacters: An optional set of characters that when pressed while this completion is active will accept it first and
|
|
||||||
then type that character. *Note* that all commit characters should have `length=1` and that superfluous
|
|
||||||
characters will be ignored.
|
|
||||||
:param Command command: An optional command that is executed *after* inserting this completion. Note: that
|
|
||||||
additional modifications to the current document should be described with the additionalTextEdits-property.
|
|
||||||
:param data: An data entry field that is preserved on a completion item between a completion and a completion resolve request.
|
|
||||||
:param float score: Score of the code completion item.
|
|
||||||
"""
|
|
||||||
self.label = label
|
|
||||||
self.kind = kind
|
|
||||||
self.detail = detail
|
|
||||||
self.documentation = documentation
|
|
||||||
self.deprecated = deprecated
|
|
||||||
self.preselect = preselect
|
|
||||||
self.sortText = sortText
|
|
||||||
self.filterText = filterText
|
|
||||||
self.insertText = insertText
|
|
||||||
self.insertTextFormat = insertTextFormat
|
|
||||||
self.textEdit = textEdit
|
|
||||||
self.additionalTextEdits = additionalTextEdits
|
|
||||||
self.commitCharacters = commitCharacters
|
|
||||||
self.command = command
|
|
||||||
self.data = data
|
|
||||||
self.score = score
|
|
||||||
|
|
||||||
|
|
||||||
class CompletionItemKind(enum.Enum):
|
|
||||||
Text = 1
|
|
||||||
Method = 2
|
|
||||||
Function = 3
|
|
||||||
Constructor = 4
|
|
||||||
Field = 5
|
|
||||||
Variable = 6
|
|
||||||
Class = 7
|
|
||||||
Interface = 8
|
|
||||||
Module = 9
|
|
||||||
Property = 10
|
|
||||||
Unit = 11
|
|
||||||
Value = 12
|
|
||||||
Enum = 13
|
|
||||||
Keyword = 14
|
|
||||||
Snippet = 15
|
|
||||||
Color = 16
|
|
||||||
File = 17
|
|
||||||
Reference = 18
|
|
||||||
Folder = 19
|
|
||||||
EnumMember = 20
|
|
||||||
Constant = 21
|
|
||||||
Struct = 22
|
|
||||||
Event = 23
|
|
||||||
Operator = 24
|
|
||||||
TypeParameter = 25
|
|
||||||
|
|
||||||
|
|
||||||
class CompletionList(object):
|
|
||||||
"""
|
|
||||||
Represents a collection of [completion items](#CompletionItem) to be preselect in the editor.
|
|
||||||
"""
|
|
||||||
def __init__(self, isIncomplete, items):
|
|
||||||
"""
|
|
||||||
Constructs a new CompletionContext instance.
|
|
||||||
|
|
||||||
:param bool isIncomplete: This list it not complete. Further typing should result in recomputing this list.
|
|
||||||
:param CompletionItem items: The completion items.
|
|
||||||
"""
|
|
||||||
self.isIncomplete = isIncomplete
|
|
||||||
self.items = [to_type(i, CompletionItem) for i in items]
|
|
||||||
|
|
||||||
class ErrorCodes(enum.Enum):
|
|
||||||
# Defined by JSON RPC
|
|
||||||
ParseError = -32700
|
|
||||||
InvalidRequest = -32600
|
|
||||||
MethodNotFound = -32601
|
|
||||||
InvalidParams = -32602
|
|
||||||
InternalError = -32603
|
|
||||||
serverErrorStart = -32099
|
|
||||||
serverErrorEnd = -32000
|
|
||||||
ServerNotInitialized = -32002
|
|
||||||
UnknownErrorCode = -32001
|
|
||||||
|
|
||||||
# Defined by the protocol.
|
|
||||||
RequestCancelled = -32800
|
|
||||||
ContentModified = -32801
|
|
||||||
|
|
||||||
class ResponseError(Exception):
|
|
||||||
def __init__(self, code, message, data = None):
|
|
||||||
self.code = code
|
|
||||||
self.message = message
|
|
||||||
if data:
|
|
||||||
self.data = data
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"""
|
|
||||||
Pligin Libs Module
|
|
||||||
"""
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
"""
|
|
||||||
__init__.py
|
|
||||||
websocket - WebSocket client library for Python
|
|
||||||
|
|
||||||
Copyright 2024 engn33r
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from ._abnf import *
|
|
||||||
from ._app import WebSocketApp as WebSocketApp, setReconnect as setReconnect
|
|
||||||
from ._core import *
|
|
||||||
from ._exceptions import *
|
|
||||||
from ._logging import *
|
|
||||||
from ._socket import *
|
|
||||||
|
|
||||||
__version__ = "1.8.0"
|
|
||||||
@@ -1,453 +0,0 @@
|
|||||||
import array
|
|
||||||
import os
|
|
||||||
import struct
|
|
||||||
import sys
|
|
||||||
from threading import Lock
|
|
||||||
from typing import Callable, Optional, Union
|
|
||||||
|
|
||||||
from ._exceptions import WebSocketPayloadException, WebSocketProtocolException
|
|
||||||
from ._utils import validate_utf8
|
|
||||||
|
|
||||||
"""
|
|
||||||
_abnf.py
|
|
||||||
websocket - WebSocket client library for Python
|
|
||||||
|
|
||||||
Copyright 2024 engn33r
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
# If wsaccel is available, use compiled routines to mask data.
|
|
||||||
# wsaccel only provides around a 10% speed boost compared
|
|
||||||
# to the websocket-client _mask() implementation.
|
|
||||||
# Note that wsaccel is unmaintained.
|
|
||||||
from wsaccel.xormask import XorMaskerSimple
|
|
||||||
|
|
||||||
def _mask(mask_value: array.array, data_value: array.array) -> bytes:
|
|
||||||
mask_result: bytes = XorMaskerSimple(mask_value).process(data_value)
|
|
||||||
return mask_result
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# wsaccel is not available, use websocket-client _mask()
|
|
||||||
native_byteorder = sys.byteorder
|
|
||||||
|
|
||||||
def _mask(mask_value: array.array, data_value: array.array) -> bytes:
|
|
||||||
datalen = len(data_value)
|
|
||||||
int_data_value = int.from_bytes(data_value, native_byteorder)
|
|
||||||
int_mask_value = int.from_bytes(
|
|
||||||
mask_value * (datalen // 4) + mask_value[: datalen % 4], native_byteorder
|
|
||||||
)
|
|
||||||
return (int_data_value ^ int_mask_value).to_bytes(datalen, native_byteorder)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"ABNF",
|
|
||||||
"continuous_frame",
|
|
||||||
"frame_buffer",
|
|
||||||
"STATUS_NORMAL",
|
|
||||||
"STATUS_GOING_AWAY",
|
|
||||||
"STATUS_PROTOCOL_ERROR",
|
|
||||||
"STATUS_UNSUPPORTED_DATA_TYPE",
|
|
||||||
"STATUS_STATUS_NOT_AVAILABLE",
|
|
||||||
"STATUS_ABNORMAL_CLOSED",
|
|
||||||
"STATUS_INVALID_PAYLOAD",
|
|
||||||
"STATUS_POLICY_VIOLATION",
|
|
||||||
"STATUS_MESSAGE_TOO_BIG",
|
|
||||||
"STATUS_INVALID_EXTENSION",
|
|
||||||
"STATUS_UNEXPECTED_CONDITION",
|
|
||||||
"STATUS_BAD_GATEWAY",
|
|
||||||
"STATUS_TLS_HANDSHAKE_ERROR",
|
|
||||||
]
|
|
||||||
|
|
||||||
# closing frame status codes.
|
|
||||||
STATUS_NORMAL = 1000
|
|
||||||
STATUS_GOING_AWAY = 1001
|
|
||||||
STATUS_PROTOCOL_ERROR = 1002
|
|
||||||
STATUS_UNSUPPORTED_DATA_TYPE = 1003
|
|
||||||
STATUS_STATUS_NOT_AVAILABLE = 1005
|
|
||||||
STATUS_ABNORMAL_CLOSED = 1006
|
|
||||||
STATUS_INVALID_PAYLOAD = 1007
|
|
||||||
STATUS_POLICY_VIOLATION = 1008
|
|
||||||
STATUS_MESSAGE_TOO_BIG = 1009
|
|
||||||
STATUS_INVALID_EXTENSION = 1010
|
|
||||||
STATUS_UNEXPECTED_CONDITION = 1011
|
|
||||||
STATUS_SERVICE_RESTART = 1012
|
|
||||||
STATUS_TRY_AGAIN_LATER = 1013
|
|
||||||
STATUS_BAD_GATEWAY = 1014
|
|
||||||
STATUS_TLS_HANDSHAKE_ERROR = 1015
|
|
||||||
|
|
||||||
VALID_CLOSE_STATUS = (
|
|
||||||
STATUS_NORMAL,
|
|
||||||
STATUS_GOING_AWAY,
|
|
||||||
STATUS_PROTOCOL_ERROR,
|
|
||||||
STATUS_UNSUPPORTED_DATA_TYPE,
|
|
||||||
STATUS_INVALID_PAYLOAD,
|
|
||||||
STATUS_POLICY_VIOLATION,
|
|
||||||
STATUS_MESSAGE_TOO_BIG,
|
|
||||||
STATUS_INVALID_EXTENSION,
|
|
||||||
STATUS_UNEXPECTED_CONDITION,
|
|
||||||
STATUS_SERVICE_RESTART,
|
|
||||||
STATUS_TRY_AGAIN_LATER,
|
|
||||||
STATUS_BAD_GATEWAY,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ABNF:
|
|
||||||
"""
|
|
||||||
ABNF frame class.
|
|
||||||
See http://tools.ietf.org/html/rfc5234
|
|
||||||
and http://tools.ietf.org/html/rfc6455#section-5.2
|
|
||||||
"""
|
|
||||||
|
|
||||||
# operation code values.
|
|
||||||
OPCODE_CONT = 0x0
|
|
||||||
OPCODE_TEXT = 0x1
|
|
||||||
OPCODE_BINARY = 0x2
|
|
||||||
OPCODE_CLOSE = 0x8
|
|
||||||
OPCODE_PING = 0x9
|
|
||||||
OPCODE_PONG = 0xA
|
|
||||||
|
|
||||||
# available operation code value tuple
|
|
||||||
OPCODES = (
|
|
||||||
OPCODE_CONT,
|
|
||||||
OPCODE_TEXT,
|
|
||||||
OPCODE_BINARY,
|
|
||||||
OPCODE_CLOSE,
|
|
||||||
OPCODE_PING,
|
|
||||||
OPCODE_PONG,
|
|
||||||
)
|
|
||||||
|
|
||||||
# opcode human readable string
|
|
||||||
OPCODE_MAP = {
|
|
||||||
OPCODE_CONT: "cont",
|
|
||||||
OPCODE_TEXT: "text",
|
|
||||||
OPCODE_BINARY: "binary",
|
|
||||||
OPCODE_CLOSE: "close",
|
|
||||||
OPCODE_PING: "ping",
|
|
||||||
OPCODE_PONG: "pong",
|
|
||||||
}
|
|
||||||
|
|
||||||
# data length threshold.
|
|
||||||
LENGTH_7 = 0x7E
|
|
||||||
LENGTH_16 = 1 << 16
|
|
||||||
LENGTH_63 = 1 << 63
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
fin: int = 0,
|
|
||||||
rsv1: int = 0,
|
|
||||||
rsv2: int = 0,
|
|
||||||
rsv3: int = 0,
|
|
||||||
opcode: int = OPCODE_TEXT,
|
|
||||||
mask_value: int = 1,
|
|
||||||
data: Union[str, bytes, None] = "",
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Constructor for ABNF. Please check RFC for arguments.
|
|
||||||
"""
|
|
||||||
self.fin = fin
|
|
||||||
self.rsv1 = rsv1
|
|
||||||
self.rsv2 = rsv2
|
|
||||||
self.rsv3 = rsv3
|
|
||||||
self.opcode = opcode
|
|
||||||
self.mask_value = mask_value
|
|
||||||
if data is None:
|
|
||||||
data = ""
|
|
||||||
self.data = data
|
|
||||||
self.get_mask_key = os.urandom
|
|
||||||
|
|
||||||
def validate(self, skip_utf8_validation: bool = False) -> None:
|
|
||||||
"""
|
|
||||||
Validate the ABNF frame.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
skip_utf8_validation: skip utf8 validation.
|
|
||||||
"""
|
|
||||||
if self.rsv1 or self.rsv2 or self.rsv3:
|
|
||||||
raise WebSocketProtocolException("rsv is not implemented, yet")
|
|
||||||
|
|
||||||
if self.opcode not in ABNF.OPCODES:
|
|
||||||
raise WebSocketProtocolException("Invalid opcode %r", self.opcode)
|
|
||||||
|
|
||||||
if self.opcode == ABNF.OPCODE_PING and not self.fin:
|
|
||||||
raise WebSocketProtocolException("Invalid ping frame.")
|
|
||||||
|
|
||||||
if self.opcode == ABNF.OPCODE_CLOSE:
|
|
||||||
l = len(self.data)
|
|
||||||
if not l:
|
|
||||||
return
|
|
||||||
if l == 1 or l >= 126:
|
|
||||||
raise WebSocketProtocolException("Invalid close frame.")
|
|
||||||
if l > 2 and not skip_utf8_validation and not validate_utf8(self.data[2:]):
|
|
||||||
raise WebSocketProtocolException("Invalid close frame.")
|
|
||||||
|
|
||||||
code = 256 * int(self.data[0]) + int(self.data[1])
|
|
||||||
if not self._is_valid_close_status(code):
|
|
||||||
raise WebSocketProtocolException("Invalid close opcode %r", code)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _is_valid_close_status(code: int) -> bool:
|
|
||||||
return code in VALID_CLOSE_STATUS or (3000 <= code < 5000)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"fin={self.fin} opcode={self.opcode} data={self.data}"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_frame(data: Union[bytes, str], opcode: int, fin: int = 1) -> "ABNF":
|
|
||||||
"""
|
|
||||||
Create frame to send text, binary and other data.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
data: str
|
|
||||||
data to send. This is string value(byte array).
|
|
||||||
If opcode is OPCODE_TEXT and this value is unicode,
|
|
||||||
data value is converted into unicode string, automatically.
|
|
||||||
opcode: int
|
|
||||||
operation code. please see OPCODE_MAP.
|
|
||||||
fin: int
|
|
||||||
fin flag. if set to 0, create continue fragmentation.
|
|
||||||
"""
|
|
||||||
if opcode == ABNF.OPCODE_TEXT and isinstance(data, str):
|
|
||||||
data = data.encode("utf-8")
|
|
||||||
# mask must be set if send data from client
|
|
||||||
return ABNF(fin, 0, 0, 0, opcode, 1, data)
|
|
||||||
|
|
||||||
def format(self) -> bytes:
|
|
||||||
"""
|
|
||||||
Format this object to string(byte array) to send data to server.
|
|
||||||
"""
|
|
||||||
if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
|
|
||||||
raise ValueError("not 0 or 1")
|
|
||||||
if self.opcode not in ABNF.OPCODES:
|
|
||||||
raise ValueError("Invalid OPCODE")
|
|
||||||
length = len(self.data)
|
|
||||||
if length >= ABNF.LENGTH_63:
|
|
||||||
raise ValueError("data is too long")
|
|
||||||
|
|
||||||
frame_header = chr(
|
|
||||||
self.fin << 7
|
|
||||||
| self.rsv1 << 6
|
|
||||||
| self.rsv2 << 5
|
|
||||||
| self.rsv3 << 4
|
|
||||||
| self.opcode
|
|
||||||
).encode("latin-1")
|
|
||||||
if length < ABNF.LENGTH_7:
|
|
||||||
frame_header += chr(self.mask_value << 7 | length).encode("latin-1")
|
|
||||||
elif length < ABNF.LENGTH_16:
|
|
||||||
frame_header += chr(self.mask_value << 7 | 0x7E).encode("latin-1")
|
|
||||||
frame_header += struct.pack("!H", length)
|
|
||||||
else:
|
|
||||||
frame_header += chr(self.mask_value << 7 | 0x7F).encode("latin-1")
|
|
||||||
frame_header += struct.pack("!Q", length)
|
|
||||||
|
|
||||||
if not self.mask_value:
|
|
||||||
if isinstance(self.data, str):
|
|
||||||
self.data = self.data.encode("utf-8")
|
|
||||||
return frame_header + self.data
|
|
||||||
mask_key = self.get_mask_key(4)
|
|
||||||
return frame_header + self._get_masked(mask_key)
|
|
||||||
|
|
||||||
def _get_masked(self, mask_key: Union[str, bytes]) -> bytes:
|
|
||||||
s = ABNF.mask(mask_key, self.data)
|
|
||||||
|
|
||||||
if isinstance(mask_key, str):
|
|
||||||
mask_key = mask_key.encode("utf-8")
|
|
||||||
|
|
||||||
return mask_key + s
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def mask(mask_key: Union[str, bytes], data: Union[str, bytes]) -> bytes:
|
|
||||||
"""
|
|
||||||
Mask or unmask data. Just do xor for each byte
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
mask_key: bytes or str
|
|
||||||
4 byte mask.
|
|
||||||
data: bytes or str
|
|
||||||
data to mask/unmask.
|
|
||||||
"""
|
|
||||||
if data is None:
|
|
||||||
data = ""
|
|
||||||
|
|
||||||
if isinstance(mask_key, str):
|
|
||||||
mask_key = mask_key.encode("latin-1")
|
|
||||||
|
|
||||||
if isinstance(data, str):
|
|
||||||
data = data.encode("latin-1")
|
|
||||||
|
|
||||||
return _mask(array.array("B", mask_key), array.array("B", data))
|
|
||||||
|
|
||||||
|
|
||||||
class frame_buffer:
|
|
||||||
_HEADER_MASK_INDEX = 5
|
|
||||||
_HEADER_LENGTH_INDEX = 6
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, recv_fn: Callable[[int], int], skip_utf8_validation: bool
|
|
||||||
) -> None:
|
|
||||||
self.recv = recv_fn
|
|
||||||
self.skip_utf8_validation = skip_utf8_validation
|
|
||||||
# Buffers over the packets from the layer beneath until desired amount
|
|
||||||
# bytes of bytes are received.
|
|
||||||
self.recv_buffer: list = []
|
|
||||||
self.clear()
|
|
||||||
self.lock = Lock()
|
|
||||||
|
|
||||||
def clear(self) -> None:
|
|
||||||
self.header: Optional[tuple] = None
|
|
||||||
self.length: Optional[int] = None
|
|
||||||
self.mask_value: Union[bytes, str, None] = None
|
|
||||||
|
|
||||||
def has_received_header(self) -> bool:
|
|
||||||
return self.header is None
|
|
||||||
|
|
||||||
def recv_header(self) -> None:
|
|
||||||
header = self.recv_strict(2)
|
|
||||||
b1 = header[0]
|
|
||||||
fin = b1 >> 7 & 1
|
|
||||||
rsv1 = b1 >> 6 & 1
|
|
||||||
rsv2 = b1 >> 5 & 1
|
|
||||||
rsv3 = b1 >> 4 & 1
|
|
||||||
opcode = b1 & 0xF
|
|
||||||
b2 = header[1]
|
|
||||||
has_mask = b2 >> 7 & 1
|
|
||||||
length_bits = b2 & 0x7F
|
|
||||||
|
|
||||||
self.header = (fin, rsv1, rsv2, rsv3, opcode, has_mask, length_bits)
|
|
||||||
|
|
||||||
def has_mask(self) -> Union[bool, int]:
|
|
||||||
if not self.header:
|
|
||||||
return False
|
|
||||||
header_val: int = self.header[frame_buffer._HEADER_MASK_INDEX]
|
|
||||||
return header_val
|
|
||||||
|
|
||||||
def has_received_length(self) -> bool:
|
|
||||||
return self.length is None
|
|
||||||
|
|
||||||
def recv_length(self) -> None:
|
|
||||||
bits = self.header[frame_buffer._HEADER_LENGTH_INDEX]
|
|
||||||
length_bits = bits & 0x7F
|
|
||||||
if length_bits == 0x7E:
|
|
||||||
v = self.recv_strict(2)
|
|
||||||
self.length = struct.unpack("!H", v)[0]
|
|
||||||
elif length_bits == 0x7F:
|
|
||||||
v = self.recv_strict(8)
|
|
||||||
self.length = struct.unpack("!Q", v)[0]
|
|
||||||
else:
|
|
||||||
self.length = length_bits
|
|
||||||
|
|
||||||
def has_received_mask(self) -> bool:
|
|
||||||
return self.mask_value is None
|
|
||||||
|
|
||||||
def recv_mask(self) -> None:
|
|
||||||
self.mask_value = self.recv_strict(4) if self.has_mask() else ""
|
|
||||||
|
|
||||||
def recv_frame(self) -> ABNF:
|
|
||||||
with self.lock:
|
|
||||||
# Header
|
|
||||||
if self.has_received_header():
|
|
||||||
self.recv_header()
|
|
||||||
(fin, rsv1, rsv2, rsv3, opcode, has_mask, _) = self.header
|
|
||||||
|
|
||||||
# Frame length
|
|
||||||
if self.has_received_length():
|
|
||||||
self.recv_length()
|
|
||||||
length = self.length
|
|
||||||
|
|
||||||
# Mask
|
|
||||||
if self.has_received_mask():
|
|
||||||
self.recv_mask()
|
|
||||||
mask_value = self.mask_value
|
|
||||||
|
|
||||||
# Payload
|
|
||||||
payload = self.recv_strict(length)
|
|
||||||
if has_mask:
|
|
||||||
payload = ABNF.mask(mask_value, payload)
|
|
||||||
|
|
||||||
# Reset for next frame
|
|
||||||
self.clear()
|
|
||||||
|
|
||||||
frame = ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload)
|
|
||||||
frame.validate(self.skip_utf8_validation)
|
|
||||||
|
|
||||||
return frame
|
|
||||||
|
|
||||||
def recv_strict(self, bufsize: int) -> bytes:
|
|
||||||
shortage = bufsize - sum(map(len, self.recv_buffer))
|
|
||||||
while shortage > 0:
|
|
||||||
# Limit buffer size that we pass to socket.recv() to avoid
|
|
||||||
# fragmenting the heap -- the number of bytes recv() actually
|
|
||||||
# reads is limited by socket buffer and is relatively small,
|
|
||||||
# yet passing large numbers repeatedly causes lots of large
|
|
||||||
# buffers allocated and then shrunk, which results in
|
|
||||||
# fragmentation.
|
|
||||||
bytes_ = self.recv(min(16384, shortage))
|
|
||||||
self.recv_buffer.append(bytes_)
|
|
||||||
shortage -= len(bytes_)
|
|
||||||
|
|
||||||
unified = b"".join(self.recv_buffer)
|
|
||||||
|
|
||||||
if shortage == 0:
|
|
||||||
self.recv_buffer = []
|
|
||||||
return unified
|
|
||||||
else:
|
|
||||||
self.recv_buffer = [unified[bufsize:]]
|
|
||||||
return unified[:bufsize]
|
|
||||||
|
|
||||||
|
|
||||||
class continuous_frame:
|
|
||||||
def __init__(self, fire_cont_frame: bool, skip_utf8_validation: bool) -> None:
|
|
||||||
self.fire_cont_frame = fire_cont_frame
|
|
||||||
self.skip_utf8_validation = skip_utf8_validation
|
|
||||||
self.cont_data: Optional[list] = None
|
|
||||||
self.recving_frames: Optional[int] = None
|
|
||||||
|
|
||||||
def validate(self, frame: ABNF) -> None:
|
|
||||||
if not self.recving_frames and frame.opcode == ABNF.OPCODE_CONT:
|
|
||||||
raise WebSocketProtocolException("Illegal frame")
|
|
||||||
if self.recving_frames and frame.opcode in (
|
|
||||||
ABNF.OPCODE_TEXT,
|
|
||||||
ABNF.OPCODE_BINARY,
|
|
||||||
):
|
|
||||||
raise WebSocketProtocolException("Illegal frame")
|
|
||||||
|
|
||||||
def add(self, frame: ABNF) -> None:
|
|
||||||
if self.cont_data:
|
|
||||||
self.cont_data[1] += frame.data
|
|
||||||
else:
|
|
||||||
if frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY):
|
|
||||||
self.recving_frames = frame.opcode
|
|
||||||
self.cont_data = [frame.opcode, frame.data]
|
|
||||||
|
|
||||||
if frame.fin:
|
|
||||||
self.recving_frames = None
|
|
||||||
|
|
||||||
def is_fire(self, frame: ABNF) -> Union[bool, int]:
|
|
||||||
return frame.fin or self.fire_cont_frame
|
|
||||||
|
|
||||||
def extract(self, frame: ABNF) -> tuple:
|
|
||||||
data = self.cont_data
|
|
||||||
self.cont_data = None
|
|
||||||
frame.data = data[1]
|
|
||||||
if (
|
|
||||||
not self.fire_cont_frame
|
|
||||||
and data[0] == ABNF.OPCODE_TEXT
|
|
||||||
and not self.skip_utf8_validation
|
|
||||||
and not validate_utf8(frame.data)
|
|
||||||
):
|
|
||||||
raise WebSocketPayloadException(f"cannot decode: {repr(frame.data)}")
|
|
||||||
return data[0], frame
|
|
||||||
@@ -1,677 +0,0 @@
|
|||||||
import inspect
|
|
||||||
import selectors
|
|
||||||
import socket
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
from typing import Any, Callable, Optional, Union
|
|
||||||
|
|
||||||
from . import _logging
|
|
||||||
from ._abnf import ABNF
|
|
||||||
from ._core import WebSocket, getdefaulttimeout
|
|
||||||
from ._exceptions import (
|
|
||||||
WebSocketConnectionClosedException,
|
|
||||||
WebSocketException,
|
|
||||||
WebSocketTimeoutException,
|
|
||||||
)
|
|
||||||
from ._ssl_compat import SSLEOFError
|
|
||||||
from ._url import parse_url
|
|
||||||
|
|
||||||
"""
|
|
||||||
_app.py
|
|
||||||
websocket - WebSocket client library for Python
|
|
||||||
|
|
||||||
Copyright 2024 engn33r
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ["WebSocketApp"]
|
|
||||||
|
|
||||||
RECONNECT = 0
|
|
||||||
|
|
||||||
|
|
||||||
def setReconnect(reconnectInterval: int) -> None:
|
|
||||||
global RECONNECT
|
|
||||||
RECONNECT = reconnectInterval
|
|
||||||
|
|
||||||
|
|
||||||
class DispatcherBase:
|
|
||||||
"""
|
|
||||||
DispatcherBase
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app: Any, ping_timeout: Union[float, int, None]) -> None:
|
|
||||||
self.app = app
|
|
||||||
self.ping_timeout = ping_timeout
|
|
||||||
|
|
||||||
def timeout(self, seconds: Union[float, int, None], callback: Callable) -> None:
|
|
||||||
time.sleep(seconds)
|
|
||||||
callback()
|
|
||||||
|
|
||||||
def reconnect(self, seconds: int, reconnector: Callable) -> None:
|
|
||||||
try:
|
|
||||||
_logging.info(
|
|
||||||
f"reconnect() - retrying in {seconds} seconds [{len(inspect.stack())} frames in stack]"
|
|
||||||
)
|
|
||||||
time.sleep(seconds)
|
|
||||||
reconnector(reconnecting=True)
|
|
||||||
except KeyboardInterrupt as e:
|
|
||||||
_logging.info(f"User exited {e}")
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
class Dispatcher(DispatcherBase):
|
|
||||||
"""
|
|
||||||
Dispatcher
|
|
||||||
"""
|
|
||||||
|
|
||||||
def read(
|
|
||||||
self,
|
|
||||||
sock: socket.socket,
|
|
||||||
read_callback: Callable,
|
|
||||||
check_callback: Callable,
|
|
||||||
) -> None:
|
|
||||||
sel = selectors.DefaultSelector()
|
|
||||||
sel.register(self.app.sock.sock, selectors.EVENT_READ)
|
|
||||||
try:
|
|
||||||
while self.app.keep_running:
|
|
||||||
if sel.select(self.ping_timeout):
|
|
||||||
if not read_callback():
|
|
||||||
break
|
|
||||||
check_callback()
|
|
||||||
finally:
|
|
||||||
sel.close()
|
|
||||||
|
|
||||||
|
|
||||||
class SSLDispatcher(DispatcherBase):
|
|
||||||
"""
|
|
||||||
SSLDispatcher
|
|
||||||
"""
|
|
||||||
|
|
||||||
def read(
|
|
||||||
self,
|
|
||||||
sock: socket.socket,
|
|
||||||
read_callback: Callable,
|
|
||||||
check_callback: Callable,
|
|
||||||
) -> None:
|
|
||||||
sock = self.app.sock.sock
|
|
||||||
sel = selectors.DefaultSelector()
|
|
||||||
sel.register(sock, selectors.EVENT_READ)
|
|
||||||
try:
|
|
||||||
while self.app.keep_running:
|
|
||||||
if self.select(sock, sel):
|
|
||||||
if not read_callback():
|
|
||||||
break
|
|
||||||
check_callback()
|
|
||||||
finally:
|
|
||||||
sel.close()
|
|
||||||
|
|
||||||
def select(self, sock, sel: selectors.DefaultSelector):
|
|
||||||
sock = self.app.sock.sock
|
|
||||||
if sock.pending():
|
|
||||||
return [
|
|
||||||
sock,
|
|
||||||
]
|
|
||||||
|
|
||||||
r = sel.select(self.ping_timeout)
|
|
||||||
|
|
||||||
if len(r) > 0:
|
|
||||||
return r[0][0]
|
|
||||||
|
|
||||||
|
|
||||||
class WrappedDispatcher:
|
|
||||||
"""
|
|
||||||
WrappedDispatcher
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app, ping_timeout: Union[float, int, None], dispatcher) -> None:
|
|
||||||
self.app = app
|
|
||||||
self.ping_timeout = ping_timeout
|
|
||||||
self.dispatcher = dispatcher
|
|
||||||
dispatcher.signal(2, dispatcher.abort) # keyboard interrupt
|
|
||||||
|
|
||||||
def read(
|
|
||||||
self,
|
|
||||||
sock: socket.socket,
|
|
||||||
read_callback: Callable,
|
|
||||||
check_callback: Callable,
|
|
||||||
) -> None:
|
|
||||||
self.dispatcher.read(sock, read_callback)
|
|
||||||
self.ping_timeout and self.timeout(self.ping_timeout, check_callback)
|
|
||||||
|
|
||||||
def timeout(self, seconds: float, callback: Callable) -> None:
|
|
||||||
self.dispatcher.timeout(seconds, callback)
|
|
||||||
|
|
||||||
def reconnect(self, seconds: int, reconnector: Callable) -> None:
|
|
||||||
self.timeout(seconds, reconnector)
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketApp:
|
|
||||||
"""
|
|
||||||
Higher level of APIs are provided. The interface is like JavaScript WebSocket object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
url: str,
|
|
||||||
header: Union[list, dict, Callable, None] = None,
|
|
||||||
on_open: Optional[Callable[[WebSocket], None]] = None,
|
|
||||||
on_reconnect: Optional[Callable[[WebSocket], None]] = None,
|
|
||||||
on_message: Optional[Callable[[WebSocket, Any], None]] = None,
|
|
||||||
on_error: Optional[Callable[[WebSocket, Any], None]] = None,
|
|
||||||
on_close: Optional[Callable[[WebSocket, Any, Any], None]] = None,
|
|
||||||
on_ping: Optional[Callable] = None,
|
|
||||||
on_pong: Optional[Callable] = None,
|
|
||||||
on_cont_message: Optional[Callable] = None,
|
|
||||||
keep_running: bool = True,
|
|
||||||
get_mask_key: Optional[Callable] = None,
|
|
||||||
cookie: Optional[str] = None,
|
|
||||||
subprotocols: Optional[list] = None,
|
|
||||||
on_data: Optional[Callable] = None,
|
|
||||||
socket: Optional[socket.socket] = None,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
WebSocketApp initialization
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
url: str
|
|
||||||
Websocket url.
|
|
||||||
header: list or dict or Callable
|
|
||||||
Custom header for websocket handshake.
|
|
||||||
If the parameter is a callable object, it is called just before the connection attempt.
|
|
||||||
The returned dict or list is used as custom header value.
|
|
||||||
This could be useful in order to properly setup timestamp dependent headers.
|
|
||||||
on_open: function
|
|
||||||
Callback object which is called at opening websocket.
|
|
||||||
on_open has one argument.
|
|
||||||
The 1st argument is this class object.
|
|
||||||
on_reconnect: function
|
|
||||||
Callback object which is called at reconnecting websocket.
|
|
||||||
on_reconnect has one argument.
|
|
||||||
The 1st argument is this class object.
|
|
||||||
on_message: function
|
|
||||||
Callback object which is called when received data.
|
|
||||||
on_message has 2 arguments.
|
|
||||||
The 1st argument is this class object.
|
|
||||||
The 2nd argument is utf-8 data received from the server.
|
|
||||||
on_error: function
|
|
||||||
Callback object which is called when we get error.
|
|
||||||
on_error has 2 arguments.
|
|
||||||
The 1st argument is this class object.
|
|
||||||
The 2nd argument is exception object.
|
|
||||||
on_close: function
|
|
||||||
Callback object which is called when connection is closed.
|
|
||||||
on_close has 3 arguments.
|
|
||||||
The 1st argument is this class object.
|
|
||||||
The 2nd argument is close_status_code.
|
|
||||||
The 3rd argument is close_msg.
|
|
||||||
on_cont_message: function
|
|
||||||
Callback object which is called when a continuation
|
|
||||||
frame is received.
|
|
||||||
on_cont_message has 3 arguments.
|
|
||||||
The 1st argument is this class object.
|
|
||||||
The 2nd argument is utf-8 string which we get from the server.
|
|
||||||
The 3rd argument is continue flag. if 0, the data continue
|
|
||||||
to next frame data
|
|
||||||
on_data: function
|
|
||||||
Callback object which is called when a message received.
|
|
||||||
This is called before on_message or on_cont_message,
|
|
||||||
and then on_message or on_cont_message is called.
|
|
||||||
on_data has 4 argument.
|
|
||||||
The 1st argument is this class object.
|
|
||||||
The 2nd argument is utf-8 string which we get from the server.
|
|
||||||
The 3rd argument is data type. ABNF.OPCODE_TEXT or ABNF.OPCODE_BINARY will be came.
|
|
||||||
The 4th argument is continue flag. If 0, the data continue
|
|
||||||
keep_running: bool
|
|
||||||
This parameter is obsolete and ignored.
|
|
||||||
get_mask_key: function
|
|
||||||
A callable function to get new mask keys, see the
|
|
||||||
WebSocket.set_mask_key's docstring for more information.
|
|
||||||
cookie: str
|
|
||||||
Cookie value.
|
|
||||||
subprotocols: list
|
|
||||||
List of available sub protocols. Default is None.
|
|
||||||
socket: socket
|
|
||||||
Pre-initialized stream socket.
|
|
||||||
"""
|
|
||||||
self.url = url
|
|
||||||
self.header = header if header is not None else []
|
|
||||||
self.cookie = cookie
|
|
||||||
|
|
||||||
self.on_open = on_open
|
|
||||||
self.on_reconnect = on_reconnect
|
|
||||||
self.on_message = on_message
|
|
||||||
self.on_data = on_data
|
|
||||||
self.on_error = on_error
|
|
||||||
self.on_close = on_close
|
|
||||||
self.on_ping = on_ping
|
|
||||||
self.on_pong = on_pong
|
|
||||||
self.on_cont_message = on_cont_message
|
|
||||||
self.keep_running = False
|
|
||||||
self.get_mask_key = get_mask_key
|
|
||||||
self.sock: Optional[WebSocket] = None
|
|
||||||
self.last_ping_tm = float(0)
|
|
||||||
self.last_pong_tm = float(0)
|
|
||||||
self.ping_thread: Optional[threading.Thread] = None
|
|
||||||
self.stop_ping: Optional[threading.Event] = None
|
|
||||||
self.ping_interval = float(0)
|
|
||||||
self.ping_timeout: Union[float, int, None] = None
|
|
||||||
self.ping_payload = ""
|
|
||||||
self.subprotocols = subprotocols
|
|
||||||
self.prepared_socket = socket
|
|
||||||
self.has_errored = False
|
|
||||||
self.has_done_teardown = False
|
|
||||||
self.has_done_teardown_lock = threading.Lock()
|
|
||||||
|
|
||||||
def send(self, data: Union[bytes, str], opcode: int = ABNF.OPCODE_TEXT) -> None:
|
|
||||||
"""
|
|
||||||
send message
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
data: str
|
|
||||||
Message to send. If you set opcode to OPCODE_TEXT,
|
|
||||||
data must be utf-8 string or unicode.
|
|
||||||
opcode: int
|
|
||||||
Operation code of data. Default is OPCODE_TEXT.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not self.sock or self.sock.send(data, opcode) == 0:
|
|
||||||
raise WebSocketConnectionClosedException("Connection is already closed.")
|
|
||||||
|
|
||||||
def send_text(self, text_data: str) -> None:
|
|
||||||
"""
|
|
||||||
Sends UTF-8 encoded text.
|
|
||||||
"""
|
|
||||||
if not self.sock or self.sock.send(text_data, ABNF.OPCODE_TEXT) == 0:
|
|
||||||
raise WebSocketConnectionClosedException("Connection is already closed.")
|
|
||||||
|
|
||||||
def send_bytes(self, data: Union[bytes, bytearray]) -> None:
|
|
||||||
"""
|
|
||||||
Sends a sequence of bytes.
|
|
||||||
"""
|
|
||||||
if not self.sock or self.sock.send(data, ABNF.OPCODE_BINARY) == 0:
|
|
||||||
raise WebSocketConnectionClosedException("Connection is already closed.")
|
|
||||||
|
|
||||||
def close(self, **kwargs) -> None:
|
|
||||||
"""
|
|
||||||
Close websocket connection.
|
|
||||||
"""
|
|
||||||
self.keep_running = False
|
|
||||||
if self.sock:
|
|
||||||
self.sock.close(**kwargs)
|
|
||||||
self.sock = None
|
|
||||||
|
|
||||||
def _start_ping_thread(self) -> None:
|
|
||||||
self.last_ping_tm = self.last_pong_tm = float(0)
|
|
||||||
self.stop_ping = threading.Event()
|
|
||||||
self.ping_thread = threading.Thread(target=self._send_ping)
|
|
||||||
self.ping_thread.daemon = True
|
|
||||||
self.ping_thread.start()
|
|
||||||
|
|
||||||
def _stop_ping_thread(self) -> None:
|
|
||||||
if self.stop_ping:
|
|
||||||
self.stop_ping.set()
|
|
||||||
if self.ping_thread and self.ping_thread.is_alive():
|
|
||||||
self.ping_thread.join(3)
|
|
||||||
self.last_ping_tm = self.last_pong_tm = float(0)
|
|
||||||
|
|
||||||
def _send_ping(self) -> None:
|
|
||||||
if self.stop_ping.wait(self.ping_interval) or self.keep_running is False:
|
|
||||||
return
|
|
||||||
while not self.stop_ping.wait(self.ping_interval) and self.keep_running is True:
|
|
||||||
if self.sock:
|
|
||||||
self.last_ping_tm = time.time()
|
|
||||||
try:
|
|
||||||
_logging.debug("Sending ping")
|
|
||||||
self.sock.ping(self.ping_payload)
|
|
||||||
except Exception as e:
|
|
||||||
_logging.debug(f"Failed to send ping: {e}")
|
|
||||||
|
|
||||||
def run_forever(
|
|
||||||
self,
|
|
||||||
sockopt: tuple = None,
|
|
||||||
sslopt: dict = None,
|
|
||||||
ping_interval: Union[float, int] = 0,
|
|
||||||
ping_timeout: Union[float, int, None] = None,
|
|
||||||
ping_payload: str = "",
|
|
||||||
http_proxy_host: str = None,
|
|
||||||
http_proxy_port: Union[int, str] = None,
|
|
||||||
http_no_proxy: list = None,
|
|
||||||
http_proxy_auth: tuple = None,
|
|
||||||
http_proxy_timeout: Optional[float] = None,
|
|
||||||
skip_utf8_validation: bool = False,
|
|
||||||
host: str = None,
|
|
||||||
origin: str = None,
|
|
||||||
dispatcher=None,
|
|
||||||
suppress_origin: bool = False,
|
|
||||||
proxy_type: str = None,
|
|
||||||
reconnect: int = None,
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
Run event loop for WebSocket framework.
|
|
||||||
|
|
||||||
This loop is an infinite loop and is alive while websocket is available.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
sockopt: tuple
|
|
||||||
Values for socket.setsockopt.
|
|
||||||
sockopt must be tuple
|
|
||||||
and each element is argument of sock.setsockopt.
|
|
||||||
sslopt: dict
|
|
||||||
Optional dict object for ssl socket option.
|
|
||||||
ping_interval: int or float
|
|
||||||
Automatically send "ping" command
|
|
||||||
every specified period (in seconds).
|
|
||||||
If set to 0, no ping is sent periodically.
|
|
||||||
ping_timeout: int or float
|
|
||||||
Timeout (in seconds) if the pong message is not received.
|
|
||||||
ping_payload: str
|
|
||||||
Payload message to send with each ping.
|
|
||||||
http_proxy_host: str
|
|
||||||
HTTP proxy host name.
|
|
||||||
http_proxy_port: int or str
|
|
||||||
HTTP proxy port. If not set, set to 80.
|
|
||||||
http_no_proxy: list
|
|
||||||
Whitelisted host names that don't use the proxy.
|
|
||||||
http_proxy_timeout: int or float
|
|
||||||
HTTP proxy timeout, default is 60 sec as per python-socks.
|
|
||||||
http_proxy_auth: tuple
|
|
||||||
HTTP proxy auth information. tuple of username and password. Default is None.
|
|
||||||
skip_utf8_validation: bool
|
|
||||||
skip utf8 validation.
|
|
||||||
host: str
|
|
||||||
update host header.
|
|
||||||
origin: str
|
|
||||||
update origin header.
|
|
||||||
dispatcher: Dispatcher object
|
|
||||||
customize reading data from socket.
|
|
||||||
suppress_origin: bool
|
|
||||||
suppress outputting origin header.
|
|
||||||
proxy_type: str
|
|
||||||
type of proxy from: http, socks4, socks4a, socks5, socks5h
|
|
||||||
reconnect: int
|
|
||||||
delay interval when reconnecting
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
teardown: bool
|
|
||||||
False if the `WebSocketApp` is closed or caught KeyboardInterrupt,
|
|
||||||
True if any other exception was raised during a loop.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if reconnect is None:
|
|
||||||
reconnect = RECONNECT
|
|
||||||
|
|
||||||
if ping_timeout is not None and ping_timeout <= 0:
|
|
||||||
raise WebSocketException("Ensure ping_timeout > 0")
|
|
||||||
if ping_interval is not None and ping_interval < 0:
|
|
||||||
raise WebSocketException("Ensure ping_interval >= 0")
|
|
||||||
if ping_timeout and ping_interval and ping_interval <= ping_timeout:
|
|
||||||
raise WebSocketException("Ensure ping_interval > ping_timeout")
|
|
||||||
if not sockopt:
|
|
||||||
sockopt = ()
|
|
||||||
if not sslopt:
|
|
||||||
sslopt = {}
|
|
||||||
if self.sock:
|
|
||||||
raise WebSocketException("socket is already opened")
|
|
||||||
|
|
||||||
self.ping_interval = ping_interval
|
|
||||||
self.ping_timeout = ping_timeout
|
|
||||||
self.ping_payload = ping_payload
|
|
||||||
self.has_done_teardown = False
|
|
||||||
self.keep_running = True
|
|
||||||
|
|
||||||
def teardown(close_frame: ABNF = None):
|
|
||||||
"""
|
|
||||||
Tears down the connection.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
close_frame: ABNF frame
|
|
||||||
If close_frame is set, the on_close handler is invoked
|
|
||||||
with the statusCode and reason from the provided frame.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# teardown() is called in many code paths to ensure resources are cleaned up and on_close is fired.
|
|
||||||
# To ensure the work is only done once, we use this bool and lock.
|
|
||||||
with self.has_done_teardown_lock:
|
|
||||||
if self.has_done_teardown:
|
|
||||||
return
|
|
||||||
self.has_done_teardown = True
|
|
||||||
|
|
||||||
self._stop_ping_thread()
|
|
||||||
self.keep_running = False
|
|
||||||
if self.sock:
|
|
||||||
self.sock.close()
|
|
||||||
close_status_code, close_reason = self._get_close_args(
|
|
||||||
close_frame if close_frame else None
|
|
||||||
)
|
|
||||||
self.sock = None
|
|
||||||
|
|
||||||
# Finally call the callback AFTER all teardown is complete
|
|
||||||
self._callback(self.on_close, close_status_code, close_reason)
|
|
||||||
|
|
||||||
def setSock(reconnecting: bool = False) -> None:
|
|
||||||
if reconnecting and self.sock:
|
|
||||||
self.sock.shutdown()
|
|
||||||
|
|
||||||
self.sock = WebSocket(
|
|
||||||
self.get_mask_key,
|
|
||||||
sockopt=sockopt,
|
|
||||||
sslopt=sslopt,
|
|
||||||
fire_cont_frame=self.on_cont_message is not None,
|
|
||||||
skip_utf8_validation=skip_utf8_validation,
|
|
||||||
enable_multithread=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.sock.settimeout(getdefaulttimeout())
|
|
||||||
try:
|
|
||||||
header = self.header() if callable(self.header) else self.header
|
|
||||||
|
|
||||||
self.sock.connect(
|
|
||||||
self.url,
|
|
||||||
header=header,
|
|
||||||
cookie=self.cookie,
|
|
||||||
http_proxy_host=http_proxy_host,
|
|
||||||
http_proxy_port=http_proxy_port,
|
|
||||||
http_no_proxy=http_no_proxy,
|
|
||||||
http_proxy_auth=http_proxy_auth,
|
|
||||||
http_proxy_timeout=http_proxy_timeout,
|
|
||||||
subprotocols=self.subprotocols,
|
|
||||||
host=host,
|
|
||||||
origin=origin,
|
|
||||||
suppress_origin=suppress_origin,
|
|
||||||
proxy_type=proxy_type,
|
|
||||||
socket=self.prepared_socket,
|
|
||||||
)
|
|
||||||
|
|
||||||
_logging.info("Websocket connected")
|
|
||||||
|
|
||||||
if self.ping_interval:
|
|
||||||
self._start_ping_thread()
|
|
||||||
|
|
||||||
if reconnecting and self.on_reconnect:
|
|
||||||
self._callback(self.on_reconnect)
|
|
||||||
else:
|
|
||||||
self._callback(self.on_open)
|
|
||||||
|
|
||||||
dispatcher.read(self.sock.sock, read, check)
|
|
||||||
except (
|
|
||||||
WebSocketConnectionClosedException,
|
|
||||||
ConnectionRefusedError,
|
|
||||||
KeyboardInterrupt,
|
|
||||||
SystemExit,
|
|
||||||
Exception,
|
|
||||||
) as e:
|
|
||||||
handleDisconnect(e, reconnecting)
|
|
||||||
|
|
||||||
def read() -> bool:
|
|
||||||
if not self.keep_running:
|
|
||||||
return teardown()
|
|
||||||
|
|
||||||
try:
|
|
||||||
op_code, frame = self.sock.recv_data_frame(True)
|
|
||||||
except (
|
|
||||||
WebSocketConnectionClosedException,
|
|
||||||
KeyboardInterrupt,
|
|
||||||
SSLEOFError,
|
|
||||||
) as e:
|
|
||||||
if custom_dispatcher:
|
|
||||||
return handleDisconnect(e, bool(reconnect))
|
|
||||||
else:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
if op_code == ABNF.OPCODE_CLOSE:
|
|
||||||
return teardown(frame)
|
|
||||||
elif op_code == ABNF.OPCODE_PING:
|
|
||||||
self._callback(self.on_ping, frame.data)
|
|
||||||
elif op_code == ABNF.OPCODE_PONG:
|
|
||||||
self.last_pong_tm = time.time()
|
|
||||||
self._callback(self.on_pong, frame.data)
|
|
||||||
elif op_code == ABNF.OPCODE_CONT and self.on_cont_message:
|
|
||||||
self._callback(self.on_data, frame.data, frame.opcode, frame.fin)
|
|
||||||
self._callback(self.on_cont_message, frame.data, frame.fin)
|
|
||||||
else:
|
|
||||||
data = frame.data
|
|
||||||
if op_code == ABNF.OPCODE_TEXT and not skip_utf8_validation:
|
|
||||||
data = data.decode("utf-8")
|
|
||||||
self._callback(self.on_data, data, frame.opcode, True)
|
|
||||||
self._callback(self.on_message, data)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def check() -> bool:
|
|
||||||
if self.ping_timeout:
|
|
||||||
has_timeout_expired = (
|
|
||||||
time.time() - self.last_ping_tm > self.ping_timeout
|
|
||||||
)
|
|
||||||
has_pong_not_arrived_after_last_ping = (
|
|
||||||
self.last_pong_tm - self.last_ping_tm < 0
|
|
||||||
)
|
|
||||||
has_pong_arrived_too_late = (
|
|
||||||
self.last_pong_tm - self.last_ping_tm > self.ping_timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
self.last_ping_tm
|
|
||||||
and has_timeout_expired
|
|
||||||
and (
|
|
||||||
has_pong_not_arrived_after_last_ping
|
|
||||||
or has_pong_arrived_too_late
|
|
||||||
)
|
|
||||||
):
|
|
||||||
raise WebSocketTimeoutException("ping/pong timed out")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def handleDisconnect(
|
|
||||||
e: Union[
|
|
||||||
WebSocketConnectionClosedException,
|
|
||||||
ConnectionRefusedError,
|
|
||||||
KeyboardInterrupt,
|
|
||||||
SystemExit,
|
|
||||||
Exception,
|
|
||||||
],
|
|
||||||
reconnecting: bool = False,
|
|
||||||
) -> bool:
|
|
||||||
self.has_errored = True
|
|
||||||
self._stop_ping_thread()
|
|
||||||
if not reconnecting:
|
|
||||||
self._callback(self.on_error, e)
|
|
||||||
|
|
||||||
if isinstance(e, (KeyboardInterrupt, SystemExit)):
|
|
||||||
teardown()
|
|
||||||
# Propagate further
|
|
||||||
raise
|
|
||||||
|
|
||||||
if reconnect:
|
|
||||||
_logging.info(f"{e} - reconnect")
|
|
||||||
if custom_dispatcher:
|
|
||||||
_logging.debug(
|
|
||||||
f"Calling custom dispatcher reconnect [{len(inspect.stack())} frames in stack]"
|
|
||||||
)
|
|
||||||
dispatcher.reconnect(reconnect, setSock)
|
|
||||||
else:
|
|
||||||
_logging.error(f"{e} - goodbye")
|
|
||||||
teardown()
|
|
||||||
|
|
||||||
custom_dispatcher = bool(dispatcher)
|
|
||||||
dispatcher = self.create_dispatcher(
|
|
||||||
ping_timeout, dispatcher, parse_url(self.url)[3]
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
setSock()
|
|
||||||
if not custom_dispatcher and reconnect:
|
|
||||||
while self.keep_running:
|
|
||||||
_logging.debug(
|
|
||||||
f"Calling dispatcher reconnect [{len(inspect.stack())} frames in stack]"
|
|
||||||
)
|
|
||||||
dispatcher.reconnect(reconnect, setSock)
|
|
||||||
except (KeyboardInterrupt, Exception) as e:
|
|
||||||
_logging.info(f"tearing down on exception {e}")
|
|
||||||
teardown()
|
|
||||||
finally:
|
|
||||||
if not custom_dispatcher:
|
|
||||||
# Ensure teardown was called before returning from run_forever
|
|
||||||
teardown()
|
|
||||||
|
|
||||||
return self.has_errored
|
|
||||||
|
|
||||||
def create_dispatcher(
|
|
||||||
self,
|
|
||||||
ping_timeout: Union[float, int, None],
|
|
||||||
dispatcher: Optional[DispatcherBase] = None,
|
|
||||||
is_ssl: bool = False,
|
|
||||||
) -> Union[Dispatcher, SSLDispatcher, WrappedDispatcher]:
|
|
||||||
if dispatcher: # If custom dispatcher is set, use WrappedDispatcher
|
|
||||||
return WrappedDispatcher(self, ping_timeout, dispatcher)
|
|
||||||
timeout = ping_timeout or 10
|
|
||||||
if is_ssl:
|
|
||||||
return SSLDispatcher(self, timeout)
|
|
||||||
return Dispatcher(self, timeout)
|
|
||||||
|
|
||||||
def _get_close_args(self, close_frame: ABNF) -> list:
|
|
||||||
"""
|
|
||||||
_get_close_args extracts the close code and reason from the close body
|
|
||||||
if it exists (RFC6455 says WebSocket Connection Close Code is optional)
|
|
||||||
"""
|
|
||||||
# Need to catch the case where close_frame is None
|
|
||||||
# Otherwise the following if statement causes an error
|
|
||||||
if not self.on_close or not close_frame:
|
|
||||||
return [None, None]
|
|
||||||
|
|
||||||
# Extract close frame status code
|
|
||||||
if close_frame.data and len(close_frame.data) >= 2:
|
|
||||||
close_status_code = 256 * int(close_frame.data[0]) + int(
|
|
||||||
close_frame.data[1]
|
|
||||||
)
|
|
||||||
reason = close_frame.data[2:]
|
|
||||||
if isinstance(reason, bytes):
|
|
||||||
reason = reason.decode("utf-8")
|
|
||||||
return [close_status_code, reason]
|
|
||||||
else:
|
|
||||||
# Most likely reached this because len(close_frame_data.data) < 2
|
|
||||||
return [None, None]
|
|
||||||
|
|
||||||
def _callback(self, callback, *args) -> None:
|
|
||||||
if callback:
|
|
||||||
try:
|
|
||||||
callback(self, *args)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
_logging.error(f"error from callback {callback}: {e}")
|
|
||||||
if self.on_error:
|
|
||||||
self.on_error(self, e)
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import http.cookies
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
"""
|
|
||||||
_cookiejar.py
|
|
||||||
websocket - WebSocket client library for Python
|
|
||||||
|
|
||||||
Copyright 2024 engn33r
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleCookieJar:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.jar: dict = {}
|
|
||||||
|
|
||||||
def add(self, set_cookie: Optional[str]) -> None:
|
|
||||||
if set_cookie:
|
|
||||||
simple_cookie = http.cookies.SimpleCookie(set_cookie)
|
|
||||||
|
|
||||||
for v in simple_cookie.values():
|
|
||||||
if domain := v.get("domain"):
|
|
||||||
if not domain.startswith("."):
|
|
||||||
domain = f".{domain}"
|
|
||||||
cookie = (
|
|
||||||
self.jar.get(domain)
|
|
||||||
if self.jar.get(domain)
|
|
||||||
else http.cookies.SimpleCookie()
|
|
||||||
)
|
|
||||||
cookie.update(simple_cookie)
|
|
||||||
self.jar[domain.lower()] = cookie
|
|
||||||
|
|
||||||
def set(self, set_cookie: str) -> None:
|
|
||||||
if set_cookie:
|
|
||||||
simple_cookie = http.cookies.SimpleCookie(set_cookie)
|
|
||||||
|
|
||||||
for v in simple_cookie.values():
|
|
||||||
if domain := v.get("domain"):
|
|
||||||
if not domain.startswith("."):
|
|
||||||
domain = f".{domain}"
|
|
||||||
self.jar[domain.lower()] = simple_cookie
|
|
||||||
|
|
||||||
def get(self, host: str) -> str:
|
|
||||||
if not host:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
cookies = []
|
|
||||||
for domain, _ in self.jar.items():
|
|
||||||
host = host.lower()
|
|
||||||
if host.endswith(domain) or host == domain[1:]:
|
|
||||||
cookies.append(self.jar.get(domain))
|
|
||||||
|
|
||||||
return "; ".join(
|
|
||||||
filter(
|
|
||||||
None,
|
|
||||||
sorted(
|
|
||||||
[
|
|
||||||
f"{k}={v.value}"
|
|
||||||
for cookie in filter(None, cookies)
|
|
||||||
for k, v in cookie.items()
|
|
||||||
]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
@@ -1,647 +0,0 @@
|
|||||||
import socket
|
|
||||||
import struct
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
from typing import Optional, Union
|
|
||||||
|
|
||||||
# websocket modules
|
|
||||||
from ._abnf import ABNF, STATUS_NORMAL, continuous_frame, frame_buffer
|
|
||||||
from ._exceptions import WebSocketProtocolException, WebSocketConnectionClosedException
|
|
||||||
from ._handshake import SUPPORTED_REDIRECT_STATUSES, handshake
|
|
||||||
from ._http import connect, proxy_info
|
|
||||||
from ._logging import debug, error, trace, isEnabledForError, isEnabledForTrace
|
|
||||||
from ._socket import getdefaulttimeout, recv, send, sock_opt
|
|
||||||
from ._ssl_compat import ssl
|
|
||||||
from ._utils import NoLock
|
|
||||||
|
|
||||||
"""
|
|
||||||
_core.py
|
|
||||||
websocket - WebSocket client library for Python
|
|
||||||
|
|
||||||
Copyright 2024 engn33r
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ["WebSocket", "create_connection"]
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocket:
|
|
||||||
"""
|
|
||||||
Low level WebSocket interface.
|
|
||||||
|
|
||||||
This class is based on the WebSocket protocol `draft-hixie-thewebsocketprotocol-76 <http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76>`_
|
|
||||||
|
|
||||||
We can connect to the websocket server and send/receive data.
|
|
||||||
The following example is an echo client.
|
|
||||||
|
|
||||||
>>> import websocket
|
|
||||||
>>> ws = websocket.WebSocket()
|
|
||||||
>>> ws.connect("ws://echo.websocket.events")
|
|
||||||
>>> ws.recv()
|
|
||||||
'echo.websocket.events sponsored by Lob.com'
|
|
||||||
>>> ws.send("Hello, Server")
|
|
||||||
19
|
|
||||||
>>> ws.recv()
|
|
||||||
'Hello, Server'
|
|
||||||
>>> ws.close()
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
get_mask_key: func
|
|
||||||
A callable function to get new mask keys, see the
|
|
||||||
WebSocket.set_mask_key's docstring for more information.
|
|
||||||
sockopt: tuple
|
|
||||||
Values for socket.setsockopt.
|
|
||||||
sockopt must be tuple and each element is argument of sock.setsockopt.
|
|
||||||
sslopt: dict
|
|
||||||
Optional dict object for ssl socket options. See FAQ for details.
|
|
||||||
fire_cont_frame: bool
|
|
||||||
Fire recv event for each cont frame. Default is False.
|
|
||||||
enable_multithread: bool
|
|
||||||
If set to True, lock send method.
|
|
||||||
skip_utf8_validation: bool
|
|
||||||
Skip utf8 validation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
get_mask_key=None,
|
|
||||||
sockopt=None,
|
|
||||||
sslopt=None,
|
|
||||||
fire_cont_frame: bool = False,
|
|
||||||
enable_multithread: bool = True,
|
|
||||||
skip_utf8_validation: bool = False,
|
|
||||||
**_,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Initialize WebSocket object.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
sslopt: dict
|
|
||||||
Optional dict object for ssl socket options. See FAQ for details.
|
|
||||||
"""
|
|
||||||
self.sock_opt = sock_opt(sockopt, sslopt)
|
|
||||||
self.handshake_response = None
|
|
||||||
self.sock: Optional[socket.socket] = None
|
|
||||||
|
|
||||||
self.connected = False
|
|
||||||
self.get_mask_key = get_mask_key
|
|
||||||
# These buffer over the build-up of a single frame.
|
|
||||||
self.frame_buffer = frame_buffer(self._recv, skip_utf8_validation)
|
|
||||||
self.cont_frame = continuous_frame(fire_cont_frame, skip_utf8_validation)
|
|
||||||
|
|
||||||
if enable_multithread:
|
|
||||||
self.lock = threading.Lock()
|
|
||||||
self.readlock = threading.Lock()
|
|
||||||
else:
|
|
||||||
self.lock = NoLock()
|
|
||||||
self.readlock = NoLock()
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
"""
|
|
||||||
Allow iteration over websocket, implying sequential `recv` executions.
|
|
||||||
"""
|
|
||||||
while True:
|
|
||||||
yield self.recv()
|
|
||||||
|
|
||||||
def __next__(self):
|
|
||||||
return self.recv()
|
|
||||||
|
|
||||||
def next(self):
|
|
||||||
return self.__next__()
|
|
||||||
|
|
||||||
def fileno(self):
|
|
||||||
return self.sock.fileno()
|
|
||||||
|
|
||||||
def set_mask_key(self, func):
|
|
||||||
"""
|
|
||||||
Set function to create mask key. You can customize mask key generator.
|
|
||||||
Mainly, this is for testing purpose.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
func: func
|
|
||||||
callable object. the func takes 1 argument as integer.
|
|
||||||
The argument means length of mask key.
|
|
||||||
This func must return string(byte array),
|
|
||||||
which length is argument specified.
|
|
||||||
"""
|
|
||||||
self.get_mask_key = func
|
|
||||||
|
|
||||||
def gettimeout(self) -> Union[float, int, None]:
|
|
||||||
"""
|
|
||||||
Get the websocket timeout (in seconds) as an int or float
|
|
||||||
|
|
||||||
Returns
|
|
||||||
----------
|
|
||||||
timeout: int or float
|
|
||||||
returns timeout value (in seconds). This value could be either float/integer.
|
|
||||||
"""
|
|
||||||
return self.sock_opt.timeout
|
|
||||||
|
|
||||||
def settimeout(self, timeout: Union[float, int, None]):
|
|
||||||
"""
|
|
||||||
Set the timeout to the websocket.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
timeout: int or float
|
|
||||||
timeout time (in seconds). This value could be either float/integer.
|
|
||||||
"""
|
|
||||||
self.sock_opt.timeout = timeout
|
|
||||||
if self.sock:
|
|
||||||
self.sock.settimeout(timeout)
|
|
||||||
|
|
||||||
timeout = property(gettimeout, settimeout)
|
|
||||||
|
|
||||||
def getsubprotocol(self):
|
|
||||||
"""
|
|
||||||
Get subprotocol
|
|
||||||
"""
|
|
||||||
if self.handshake_response:
|
|
||||||
return self.handshake_response.subprotocol
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
subprotocol = property(getsubprotocol)
|
|
||||||
|
|
||||||
def getstatus(self):
|
|
||||||
"""
|
|
||||||
Get handshake status
|
|
||||||
"""
|
|
||||||
if self.handshake_response:
|
|
||||||
return self.handshake_response.status
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
status = property(getstatus)
|
|
||||||
|
|
||||||
def getheaders(self):
|
|
||||||
"""
|
|
||||||
Get handshake response header
|
|
||||||
"""
|
|
||||||
if self.handshake_response:
|
|
||||||
return self.handshake_response.headers
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def is_ssl(self):
|
|
||||||
try:
|
|
||||||
return isinstance(self.sock, ssl.SSLSocket)
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
headers = property(getheaders)
|
|
||||||
|
|
||||||
def connect(self, url, **options):
|
|
||||||
"""
|
|
||||||
Connect to url. url is websocket url scheme.
|
|
||||||
ie. ws://host:port/resource
|
|
||||||
You can customize using 'options'.
|
|
||||||
If you set "header" list object, you can set your own custom header.
|
|
||||||
|
|
||||||
>>> ws = WebSocket()
|
|
||||||
>>> ws.connect("ws://echo.websocket.events",
|
|
||||||
... header=["User-Agent: MyProgram",
|
|
||||||
... "x-custom: header"])
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
header: list or dict
|
|
||||||
Custom http header list or dict.
|
|
||||||
cookie: str
|
|
||||||
Cookie value.
|
|
||||||
origin: str
|
|
||||||
Custom origin url.
|
|
||||||
connection: str
|
|
||||||
Custom connection header value.
|
|
||||||
Default value "Upgrade" set in _handshake.py
|
|
||||||
suppress_origin: bool
|
|
||||||
Suppress outputting origin header.
|
|
||||||
host: str
|
|
||||||
Custom host header string.
|
|
||||||
timeout: int or float
|
|
||||||
Socket timeout time. This value is an integer or float.
|
|
||||||
If you set None for this value, it means "use default_timeout value"
|
|
||||||
http_proxy_host: str
|
|
||||||
HTTP proxy host name.
|
|
||||||
http_proxy_port: str or int
|
|
||||||
HTTP proxy port. Default is 80.
|
|
||||||
http_no_proxy: list
|
|
||||||
Whitelisted host names that don't use the proxy.
|
|
||||||
http_proxy_auth: tuple
|
|
||||||
HTTP proxy auth information. Tuple of username and password. Default is None.
|
|
||||||
http_proxy_timeout: int or float
|
|
||||||
HTTP proxy timeout, default is 60 sec as per python-socks.
|
|
||||||
redirect_limit: int
|
|
||||||
Number of redirects to follow.
|
|
||||||
subprotocols: list
|
|
||||||
List of available subprotocols. Default is None.
|
|
||||||
socket: socket
|
|
||||||
Pre-initialized stream socket.
|
|
||||||
"""
|
|
||||||
self.sock_opt.timeout = options.get("timeout", self.sock_opt.timeout)
|
|
||||||
self.sock, addrs = connect(
|
|
||||||
url, self.sock_opt, proxy_info(**options), options.pop("socket", None)
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.handshake_response = handshake(self.sock, url, *addrs, **options)
|
|
||||||
for _ in range(options.pop("redirect_limit", 3)):
|
|
||||||
if self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES:
|
|
||||||
url = self.handshake_response.headers["location"]
|
|
||||||
self.sock.close()
|
|
||||||
self.sock, addrs = connect(
|
|
||||||
url,
|
|
||||||
self.sock_opt,
|
|
||||||
proxy_info(**options),
|
|
||||||
options.pop("socket", None),
|
|
||||||
)
|
|
||||||
self.handshake_response = handshake(
|
|
||||||
self.sock, url, *addrs, **options
|
|
||||||
)
|
|
||||||
self.connected = True
|
|
||||||
except:
|
|
||||||
if self.sock:
|
|
||||||
self.sock.close()
|
|
||||||
self.sock = None
|
|
||||||
raise
|
|
||||||
|
|
||||||
def send(self, payload: Union[bytes, str], opcode: int = ABNF.OPCODE_TEXT) -> int:
|
|
||||||
"""
|
|
||||||
Send the data as string.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
payload: str
|
|
||||||
Payload must be utf-8 string or unicode,
|
|
||||||
If the opcode is OPCODE_TEXT.
|
|
||||||
Otherwise, it must be string(byte array).
|
|
||||||
opcode: int
|
|
||||||
Operation code (opcode) to send.
|
|
||||||
"""
|
|
||||||
|
|
||||||
frame = ABNF.create_frame(payload, opcode)
|
|
||||||
return self.send_frame(frame)
|
|
||||||
|
|
||||||
def send_text(self, text_data: str) -> int:
|
|
||||||
"""
|
|
||||||
Sends UTF-8 encoded text.
|
|
||||||
"""
|
|
||||||
return self.send(text_data, ABNF.OPCODE_TEXT)
|
|
||||||
|
|
||||||
def send_bytes(self, data: Union[bytes, bytearray]) -> int:
|
|
||||||
"""
|
|
||||||
Sends a sequence of bytes.
|
|
||||||
"""
|
|
||||||
return self.send(data, ABNF.OPCODE_BINARY)
|
|
||||||
|
|
||||||
def send_frame(self, frame) -> int:
|
|
||||||
"""
|
|
||||||
Send the data frame.
|
|
||||||
|
|
||||||
>>> ws = create_connection("ws://echo.websocket.events")
|
|
||||||
>>> frame = ABNF.create_frame("Hello", ABNF.OPCODE_TEXT)
|
|
||||||
>>> ws.send_frame(frame)
|
|
||||||
>>> cont_frame = ABNF.create_frame("My name is ", ABNF.OPCODE_CONT, 0)
|
|
||||||
>>> ws.send_frame(frame)
|
|
||||||
>>> cont_frame = ABNF.create_frame("Foo Bar", ABNF.OPCODE_CONT, 1)
|
|
||||||
>>> ws.send_frame(frame)
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
frame: ABNF frame
|
|
||||||
frame data created by ABNF.create_frame
|
|
||||||
"""
|
|
||||||
if self.get_mask_key:
|
|
||||||
frame.get_mask_key = self.get_mask_key
|
|
||||||
data = frame.format()
|
|
||||||
length = len(data)
|
|
||||||
if isEnabledForTrace():
|
|
||||||
trace(f"++Sent raw: {repr(data)}")
|
|
||||||
trace(f"++Sent decoded: {frame.__str__()}")
|
|
||||||
with self.lock:
|
|
||||||
while data:
|
|
||||||
l = self._send(data)
|
|
||||||
data = data[l:]
|
|
||||||
|
|
||||||
return length
|
|
||||||
|
|
||||||
def send_binary(self, payload: bytes) -> int:
|
|
||||||
"""
|
|
||||||
Send a binary message (OPCODE_BINARY).
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
payload: bytes
|
|
||||||
payload of message to send.
|
|
||||||
"""
|
|
||||||
return self.send(payload, ABNF.OPCODE_BINARY)
|
|
||||||
|
|
||||||
def ping(self, payload: Union[str, bytes] = ""):
|
|
||||||
"""
|
|
||||||
Send ping data.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
payload: str
|
|
||||||
data payload to send server.
|
|
||||||
"""
|
|
||||||
if isinstance(payload, str):
|
|
||||||
payload = payload.encode("utf-8")
|
|
||||||
self.send(payload, ABNF.OPCODE_PING)
|
|
||||||
|
|
||||||
def pong(self, payload: Union[str, bytes] = ""):
|
|
||||||
"""
|
|
||||||
Send pong data.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
payload: str
|
|
||||||
data payload to send server.
|
|
||||||
"""
|
|
||||||
if isinstance(payload, str):
|
|
||||||
payload = payload.encode("utf-8")
|
|
||||||
self.send(payload, ABNF.OPCODE_PONG)
|
|
||||||
|
|
||||||
def recv(self) -> Union[str, bytes]:
|
|
||||||
"""
|
|
||||||
Receive string data(byte array) from the server.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
----------
|
|
||||||
data: string (byte array) value.
|
|
||||||
"""
|
|
||||||
with self.readlock:
|
|
||||||
opcode, data = self.recv_data()
|
|
||||||
if opcode == ABNF.OPCODE_TEXT:
|
|
||||||
data_received: Union[bytes, str] = data
|
|
||||||
if isinstance(data_received, bytes):
|
|
||||||
return data_received.decode("utf-8")
|
|
||||||
elif isinstance(data_received, str):
|
|
||||||
return data_received
|
|
||||||
elif opcode == ABNF.OPCODE_BINARY:
|
|
||||||
data_binary: bytes = data
|
|
||||||
return data_binary
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def recv_data(self, control_frame: bool = False) -> tuple:
|
|
||||||
"""
|
|
||||||
Receive data with operation code.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
control_frame: bool
|
|
||||||
a boolean flag indicating whether to return control frame
|
|
||||||
data, defaults to False
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
opcode, frame.data: tuple
|
|
||||||
tuple of operation code and string(byte array) value.
|
|
||||||
"""
|
|
||||||
opcode, frame = self.recv_data_frame(control_frame)
|
|
||||||
return opcode, frame.data
|
|
||||||
|
|
||||||
def recv_data_frame(self, control_frame: bool = False) -> tuple:
|
|
||||||
"""
|
|
||||||
Receive data with operation code.
|
|
||||||
|
|
||||||
If a valid ping message is received, a pong response is sent.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
control_frame: bool
|
|
||||||
a boolean flag indicating whether to return control frame
|
|
||||||
data, defaults to False
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
frame.opcode, frame: tuple
|
|
||||||
tuple of operation code and string(byte array) value.
|
|
||||||
"""
|
|
||||||
while True:
|
|
||||||
frame = self.recv_frame()
|
|
||||||
if isEnabledForTrace():
|
|
||||||
trace(f"++Rcv raw: {repr(frame.format())}")
|
|
||||||
trace(f"++Rcv decoded: {frame.__str__()}")
|
|
||||||
if not frame:
|
|
||||||
# handle error:
|
|
||||||
# 'NoneType' object has no attribute 'opcode'
|
|
||||||
raise WebSocketProtocolException(f"Not a valid frame {frame}")
|
|
||||||
elif frame.opcode in (
|
|
||||||
ABNF.OPCODE_TEXT,
|
|
||||||
ABNF.OPCODE_BINARY,
|
|
||||||
ABNF.OPCODE_CONT,
|
|
||||||
):
|
|
||||||
self.cont_frame.validate(frame)
|
|
||||||
self.cont_frame.add(frame)
|
|
||||||
|
|
||||||
if self.cont_frame.is_fire(frame):
|
|
||||||
return self.cont_frame.extract(frame)
|
|
||||||
|
|
||||||
elif frame.opcode == ABNF.OPCODE_CLOSE:
|
|
||||||
self.send_close()
|
|
||||||
return frame.opcode, frame
|
|
||||||
elif frame.opcode == ABNF.OPCODE_PING:
|
|
||||||
if len(frame.data) < 126:
|
|
||||||
self.pong(frame.data)
|
|
||||||
else:
|
|
||||||
raise WebSocketProtocolException("Ping message is too long")
|
|
||||||
if control_frame:
|
|
||||||
return frame.opcode, frame
|
|
||||||
elif frame.opcode == ABNF.OPCODE_PONG:
|
|
||||||
if control_frame:
|
|
||||||
return frame.opcode, frame
|
|
||||||
|
|
||||||
def recv_frame(self):
|
|
||||||
"""
|
|
||||||
Receive data as frame from server.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
self.frame_buffer.recv_frame(): ABNF frame object
|
|
||||||
"""
|
|
||||||
return self.frame_buffer.recv_frame()
|
|
||||||
|
|
||||||
def send_close(self, status: int = STATUS_NORMAL, reason: bytes = b""):
|
|
||||||
"""
|
|
||||||
Send close data to the server.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
status: int
|
|
||||||
Status code to send. See STATUS_XXX.
|
|
||||||
reason: str or bytes
|
|
||||||
The reason to close. This must be string or UTF-8 bytes.
|
|
||||||
"""
|
|
||||||
if status < 0 or status >= ABNF.LENGTH_16:
|
|
||||||
raise ValueError("code is invalid range")
|
|
||||||
self.connected = False
|
|
||||||
self.send(struct.pack("!H", status) + reason, ABNF.OPCODE_CLOSE)
|
|
||||||
|
|
||||||
def close(self, status: int = STATUS_NORMAL, reason: bytes = b"", timeout: int = 3):
|
|
||||||
"""
|
|
||||||
Close Websocket object
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
status: int
|
|
||||||
Status code to send. See VALID_CLOSE_STATUS in ABNF.
|
|
||||||
reason: bytes
|
|
||||||
The reason to close in UTF-8.
|
|
||||||
timeout: int or float
|
|
||||||
Timeout until receive a close frame.
|
|
||||||
If None, it will wait forever until receive a close frame.
|
|
||||||
"""
|
|
||||||
if not self.connected:
|
|
||||||
return
|
|
||||||
if status < 0 or status >= ABNF.LENGTH_16:
|
|
||||||
raise ValueError("code is invalid range")
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.connected = False
|
|
||||||
self.send(struct.pack("!H", status) + reason, ABNF.OPCODE_CLOSE)
|
|
||||||
sock_timeout = self.sock.gettimeout()
|
|
||||||
self.sock.settimeout(timeout)
|
|
||||||
start_time = time.time()
|
|
||||||
while timeout is None or time.time() - start_time < timeout:
|
|
||||||
try:
|
|
||||||
frame = self.recv_frame()
|
|
||||||
if frame.opcode != ABNF.OPCODE_CLOSE:
|
|
||||||
continue
|
|
||||||
if isEnabledForError():
|
|
||||||
recv_status = struct.unpack("!H", frame.data[0:2])[0]
|
|
||||||
if recv_status >= 3000 and recv_status <= 4999:
|
|
||||||
debug(f"close status: {repr(recv_status)}")
|
|
||||||
elif recv_status != STATUS_NORMAL:
|
|
||||||
error(f"close status: {repr(recv_status)}")
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
break
|
|
||||||
self.sock.settimeout(sock_timeout)
|
|
||||||
self.sock.shutdown(socket.SHUT_RDWR)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.shutdown()
|
|
||||||
|
|
||||||
def abort(self):
|
|
||||||
"""
|
|
||||||
Low-level asynchronous abort, wakes up other threads that are waiting in recv_*
|
|
||||||
"""
|
|
||||||
if self.connected:
|
|
||||||
self.sock.shutdown(socket.SHUT_RDWR)
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
"""
|
|
||||||
close socket, immediately.
|
|
||||||
"""
|
|
||||||
if self.sock:
|
|
||||||
self.sock.close()
|
|
||||||
self.sock = None
|
|
||||||
self.connected = False
|
|
||||||
|
|
||||||
def _send(self, data: Union[str, bytes]):
|
|
||||||
return send(self.sock, data)
|
|
||||||
|
|
||||||
def _recv(self, bufsize):
|
|
||||||
try:
|
|
||||||
return recv(self.sock, bufsize)
|
|
||||||
except WebSocketConnectionClosedException:
|
|
||||||
if self.sock:
|
|
||||||
self.sock.close()
|
|
||||||
self.sock = None
|
|
||||||
self.connected = False
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def create_connection(url: str, timeout=None, class_=WebSocket, **options):
|
|
||||||
"""
|
|
||||||
Connect to url and return websocket object.
|
|
||||||
|
|
||||||
Connect to url and return the WebSocket object.
|
|
||||||
Passing optional timeout parameter will set the timeout on the socket.
|
|
||||||
If no timeout is supplied,
|
|
||||||
the global default timeout setting returned by getdefaulttimeout() is used.
|
|
||||||
You can customize using 'options'.
|
|
||||||
If you set "header" list object, you can set your own custom header.
|
|
||||||
|
|
||||||
>>> conn = create_connection("ws://echo.websocket.events",
|
|
||||||
... header=["User-Agent: MyProgram",
|
|
||||||
... "x-custom: header"])
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
class_: class
|
|
||||||
class to instantiate when creating the connection. It has to implement
|
|
||||||
settimeout and connect. It's __init__ should be compatible with
|
|
||||||
WebSocket.__init__, i.e. accept all of it's kwargs.
|
|
||||||
header: list or dict
|
|
||||||
custom http header list or dict.
|
|
||||||
cookie: str
|
|
||||||
Cookie value.
|
|
||||||
origin: str
|
|
||||||
custom origin url.
|
|
||||||
suppress_origin: bool
|
|
||||||
suppress outputting origin header.
|
|
||||||
host: str
|
|
||||||
custom host header string.
|
|
||||||
timeout: int or float
|
|
||||||
socket timeout time. This value could be either float/integer.
|
|
||||||
If set to None, it uses the default_timeout value.
|
|
||||||
http_proxy_host: str
|
|
||||||
HTTP proxy host name.
|
|
||||||
http_proxy_port: str or int
|
|
||||||
HTTP proxy port. If not set, set to 80.
|
|
||||||
http_no_proxy: list
|
|
||||||
Whitelisted host names that don't use the proxy.
|
|
||||||
http_proxy_auth: tuple
|
|
||||||
HTTP proxy auth information. tuple of username and password. Default is None.
|
|
||||||
http_proxy_timeout: int or float
|
|
||||||
HTTP proxy timeout, default is 60 sec as per python-socks.
|
|
||||||
enable_multithread: bool
|
|
||||||
Enable lock for multithread.
|
|
||||||
redirect_limit: int
|
|
||||||
Number of redirects to follow.
|
|
||||||
sockopt: tuple
|
|
||||||
Values for socket.setsockopt.
|
|
||||||
sockopt must be a tuple and each element is an argument of sock.setsockopt.
|
|
||||||
sslopt: dict
|
|
||||||
Optional dict object for ssl socket options. See FAQ for details.
|
|
||||||
subprotocols: list
|
|
||||||
List of available subprotocols. Default is None.
|
|
||||||
skip_utf8_validation: bool
|
|
||||||
Skip utf8 validation.
|
|
||||||
socket: socket
|
|
||||||
Pre-initialized stream socket.
|
|
||||||
"""
|
|
||||||
sockopt = options.pop("sockopt", [])
|
|
||||||
sslopt = options.pop("sslopt", {})
|
|
||||||
fire_cont_frame = options.pop("fire_cont_frame", False)
|
|
||||||
enable_multithread = options.pop("enable_multithread", True)
|
|
||||||
skip_utf8_validation = options.pop("skip_utf8_validation", False)
|
|
||||||
websock = class_(
|
|
||||||
sockopt=sockopt,
|
|
||||||
sslopt=sslopt,
|
|
||||||
fire_cont_frame=fire_cont_frame,
|
|
||||||
enable_multithread=enable_multithread,
|
|
||||||
skip_utf8_validation=skip_utf8_validation,
|
|
||||||
**options,
|
|
||||||
)
|
|
||||||
websock.settimeout(timeout if timeout is not None else getdefaulttimeout())
|
|
||||||
websock.connect(url, **options)
|
|
||||||
return websock
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
"""
|
|
||||||
_exceptions.py
|
|
||||||
websocket - WebSocket client library for Python
|
|
||||||
|
|
||||||
Copyright 2024 engn33r
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketException(Exception):
|
|
||||||
"""
|
|
||||||
WebSocket exception class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketProtocolException(WebSocketException):
|
|
||||||
"""
|
|
||||||
If the WebSocket protocol is invalid, this exception will be raised.
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketPayloadException(WebSocketException):
|
|
||||||
"""
|
|
||||||
If the WebSocket payload is invalid, this exception will be raised.
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketConnectionClosedException(WebSocketException):
|
|
||||||
"""
|
|
||||||
If remote host closed the connection or some network error happened,
|
|
||||||
this exception will be raised.
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketTimeoutException(WebSocketException):
|
|
||||||
"""
|
|
||||||
WebSocketTimeoutException will be raised at socket timeout during read/write data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketProxyException(WebSocketException):
|
|
||||||
"""
|
|
||||||
WebSocketProxyException will be raised when proxy error occurred.
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketBadStatusException(WebSocketException):
|
|
||||||
"""
|
|
||||||
WebSocketBadStatusException will be raised when we get bad handshake status code.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
message: str,
|
|
||||||
status_code: int,
|
|
||||||
status_message=None,
|
|
||||||
resp_headers=None,
|
|
||||||
resp_body=None,
|
|
||||||
):
|
|
||||||
super().__init__(message)
|
|
||||||
self.status_code = status_code
|
|
||||||
self.resp_headers = resp_headers
|
|
||||||
self.resp_body = resp_body
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketAddressException(WebSocketException):
|
|
||||||
"""
|
|
||||||
If the websocket address info cannot be found, this exception will be raised.
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
"""
|
|
||||||
_handshake.py
|
|
||||||
websocket - WebSocket client library for Python
|
|
||||||
|
|
||||||
Copyright 2024 engn33r
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import hashlib
|
|
||||||
import hmac
|
|
||||||
import os
|
|
||||||
from base64 import encodebytes as base64encode
|
|
||||||
from http import HTTPStatus
|
|
||||||
|
|
||||||
from ._cookiejar import SimpleCookieJar
|
|
||||||
from ._exceptions import WebSocketException, WebSocketBadStatusException
|
|
||||||
from ._http import read_headers
|
|
||||||
from ._logging import dump, error
|
|
||||||
from ._socket import send
|
|
||||||
|
|
||||||
__all__ = ["handshake_response", "handshake", "SUPPORTED_REDIRECT_STATUSES"]
|
|
||||||
|
|
||||||
# websocket supported version.
|
|
||||||
VERSION = 13
|
|
||||||
|
|
||||||
SUPPORTED_REDIRECT_STATUSES = (
|
|
||||||
HTTPStatus.MOVED_PERMANENTLY,
|
|
||||||
HTTPStatus.FOUND,
|
|
||||||
HTTPStatus.SEE_OTHER,
|
|
||||||
HTTPStatus.TEMPORARY_REDIRECT,
|
|
||||||
HTTPStatus.PERMANENT_REDIRECT,
|
|
||||||
)
|
|
||||||
SUCCESS_STATUSES = SUPPORTED_REDIRECT_STATUSES + (HTTPStatus.SWITCHING_PROTOCOLS,)
|
|
||||||
|
|
||||||
CookieJar = SimpleCookieJar()
|
|
||||||
|
|
||||||
|
|
||||||
class handshake_response:
|
|
||||||
def __init__(self, status: int, headers: dict, subprotocol):
|
|
||||||
self.status = status
|
|
||||||
self.headers = headers
|
|
||||||
self.subprotocol = subprotocol
|
|
||||||
CookieJar.add(headers.get("set-cookie"))
|
|
||||||
|
|
||||||
|
|
||||||
def handshake(
|
|
||||||
sock, url: str, hostname: str, port: int, resource: str, **options
|
|
||||||
) -> handshake_response:
|
|
||||||
headers, key = _get_handshake_headers(resource, url, hostname, port, options)
|
|
||||||
|
|
||||||
header_str = "\r\n".join(headers)
|
|
||||||
send(sock, header_str)
|
|
||||||
dump("request header", header_str)
|
|
||||||
|
|
||||||
status, resp = _get_resp_headers(sock)
|
|
||||||
if status in SUPPORTED_REDIRECT_STATUSES:
|
|
||||||
return handshake_response(status, resp, None)
|
|
||||||
success, subproto = _validate(resp, key, options.get("subprotocols"))
|
|
||||||
if not success:
|
|
||||||
raise WebSocketException("Invalid WebSocket Header")
|
|
||||||
|
|
||||||
return handshake_response(status, resp, subproto)
|
|
||||||
|
|
||||||
|
|
||||||
def _pack_hostname(hostname: str) -> str:
|
|
||||||
# IPv6 address
|
|
||||||
if ":" in hostname:
|
|
||||||
return f"[{hostname}]"
|
|
||||||
return hostname
|
|
||||||
|
|
||||||
|
|
||||||
def _get_handshake_headers(
|
|
||||||
resource: str, url: str, host: str, port: int, options: dict
|
|
||||||
) -> tuple:
|
|
||||||
headers = [f"GET {resource} HTTP/1.1", "Upgrade: websocket"]
|
|
||||||
if port in [80, 443]:
|
|
||||||
hostport = _pack_hostname(host)
|
|
||||||
else:
|
|
||||||
hostport = f"{_pack_hostname(host)}:{port}"
|
|
||||||
if options.get("host"):
|
|
||||||
headers.append(f'Host: {options["host"]}')
|
|
||||||
else:
|
|
||||||
headers.append(f"Host: {hostport}")
|
|
||||||
|
|
||||||
# scheme indicates whether http or https is used in Origin
|
|
||||||
# The same approach is used in parse_url of _url.py to set default port
|
|
||||||
scheme, url = url.split(":", 1)
|
|
||||||
if not options.get("suppress_origin"):
|
|
||||||
if "origin" in options and options["origin"] is not None:
|
|
||||||
headers.append(f'Origin: {options["origin"]}')
|
|
||||||
elif scheme == "wss":
|
|
||||||
headers.append(f"Origin: https://{hostport}")
|
|
||||||
else:
|
|
||||||
headers.append(f"Origin: http://{hostport}")
|
|
||||||
|
|
||||||
key = _create_sec_websocket_key()
|
|
||||||
|
|
||||||
# Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified
|
|
||||||
if not options.get("header") or "Sec-WebSocket-Key" not in options["header"]:
|
|
||||||
headers.append(f"Sec-WebSocket-Key: {key}")
|
|
||||||
else:
|
|
||||||
key = options["header"]["Sec-WebSocket-Key"]
|
|
||||||
|
|
||||||
if not options.get("header") or "Sec-WebSocket-Version" not in options["header"]:
|
|
||||||
headers.append(f"Sec-WebSocket-Version: {VERSION}")
|
|
||||||
|
|
||||||
if not options.get("connection"):
|
|
||||||
headers.append("Connection: Upgrade")
|
|
||||||
else:
|
|
||||||
headers.append(options["connection"])
|
|
||||||
|
|
||||||
if subprotocols := options.get("subprotocols"):
|
|
||||||
headers.append(f'Sec-WebSocket-Protocol: {",".join(subprotocols)}')
|
|
||||||
|
|
||||||
if header := options.get("header"):
|
|
||||||
if isinstance(header, dict):
|
|
||||||
header = [": ".join([k, v]) for k, v in header.items() if v is not None]
|
|
||||||
headers.extend(header)
|
|
||||||
|
|
||||||
server_cookie = CookieJar.get(host)
|
|
||||||
client_cookie = options.get("cookie", None)
|
|
||||||
|
|
||||||
if cookie := "; ".join(filter(None, [server_cookie, client_cookie])):
|
|
||||||
headers.append(f"Cookie: {cookie}")
|
|
||||||
|
|
||||||
headers.extend(("", ""))
|
|
||||||
return headers, key
|
|
||||||
|
|
||||||
|
|
||||||
def _get_resp_headers(sock, success_statuses: tuple = SUCCESS_STATUSES) -> tuple:
|
|
||||||
status, resp_headers, status_message = read_headers(sock)
|
|
||||||
if status not in success_statuses:
|
|
||||||
content_len = resp_headers.get("content-length")
|
|
||||||
if content_len:
|
|
||||||
response_body = sock.recv(
|
|
||||||
int(content_len)
|
|
||||||
) # read the body of the HTTP error message response and include it in the exception
|
|
||||||
else:
|
|
||||||
response_body = None
|
|
||||||
raise WebSocketBadStatusException(
|
|
||||||
f"Handshake status {status} {status_message} -+-+- {resp_headers} -+-+- {response_body}",
|
|
||||||
status,
|
|
||||||
status_message,
|
|
||||||
resp_headers,
|
|
||||||
response_body,
|
|
||||||
)
|
|
||||||
return status, resp_headers
|
|
||||||
|
|
||||||
|
|
||||||
_HEADERS_TO_CHECK = {
|
|
||||||
"upgrade": "websocket",
|
|
||||||
"connection": "upgrade",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _validate(headers, key: str, subprotocols) -> tuple:
|
|
||||||
subproto = None
|
|
||||||
for k, v in _HEADERS_TO_CHECK.items():
|
|
||||||
r = headers.get(k, None)
|
|
||||||
if not r:
|
|
||||||
return False, None
|
|
||||||
r = [x.strip().lower() for x in r.split(",")]
|
|
||||||
if v not in r:
|
|
||||||
return False, None
|
|
||||||
|
|
||||||
if subprotocols:
|
|
||||||
subproto = headers.get("sec-websocket-protocol", None)
|
|
||||||
if not subproto or subproto.lower() not in [s.lower() for s in subprotocols]:
|
|
||||||
error(f"Invalid subprotocol: {subprotocols}")
|
|
||||||
return False, None
|
|
||||||
subproto = subproto.lower()
|
|
||||||
|
|
||||||
result = headers.get("sec-websocket-accept", None)
|
|
||||||
if not result:
|
|
||||||
return False, None
|
|
||||||
result = result.lower()
|
|
||||||
|
|
||||||
if isinstance(result, str):
|
|
||||||
result = result.encode("utf-8")
|
|
||||||
|
|
||||||
value = f"{key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11".encode("utf-8")
|
|
||||||
hashed = base64encode(hashlib.sha1(value).digest()).strip().lower()
|
|
||||||
|
|
||||||
if hmac.compare_digest(hashed, result):
|
|
||||||
return True, subproto
|
|
||||||
else:
|
|
||||||
return False, None
|
|
||||||
|
|
||||||
|
|
||||||
def _create_sec_websocket_key() -> str:
|
|
||||||
randomness = os.urandom(16)
|
|
||||||
return base64encode(randomness).decode("utf-8").strip()
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user