Removed Sublime Text plugin
This commit is contained in:
parent
9340ae44cc
commit
75b3a24aec
9 changed files with 0 additions and 960 deletions
|
@ -1,11 +0,0 @@
|
||||||
%YAML 1.2
|
|
||||||
---
|
|
||||||
# http://www.sublimetext.com/docs/3/syntax.html
|
|
||||||
name: Compile Highlight
|
|
||||||
file_extensions: []
|
|
||||||
hidden: true
|
|
||||||
scope: source.inspect
|
|
||||||
contexts:
|
|
||||||
main:
|
|
||||||
- match: \b(warning|error)\b
|
|
||||||
scope: keyword.control.c
|
|
|
@ -1,14 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"keys" : ["f8"],
|
|
||||||
"command" : "run"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"keys" : ["f9"],
|
|
||||||
"command" : "stop"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"keys" : ["f10"],
|
|
||||||
"command" : "sensor"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,14 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"keys" : ["f8"],
|
|
||||||
"command" : "run"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"keys" : ["f9"],
|
|
||||||
"command" : "stop"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"keys" : ["f10"],
|
|
||||||
"command" : "sensor"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,14 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"keys" : ["f8"],
|
|
||||||
"command" : "run"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"keys" : ["f9"],
|
|
||||||
"command" : "stop"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"keys" : ["f10"],
|
|
||||||
"command" : "sensor"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1 +0,0 @@
|
||||||
../../Shared
|
|
|
@ -1,140 +0,0 @@
|
||||||
FUNCTION = type(lambda: 1)
|
|
||||||
|
|
||||||
class Input:
|
|
||||||
def __init__(self, caption, initial_text="", on_done=None, on_change=None,
|
|
||||||
on_cancel=None, kwargs={}):
|
|
||||||
self.caption = caption
|
|
||||||
self.initial_text = initial_text
|
|
||||||
self.on_done = on_done
|
|
||||||
self.on_change = on_change
|
|
||||||
self.on_cancel = on_cancel
|
|
||||||
self.kwargs = kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def wrapped_on_done(self, input_):
|
|
||||||
if not self.on_done == None:
|
|
||||||
self.on_done(input_, **self.kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def wrapped_on_change(self, input_):
|
|
||||||
if not self.on_change == None:
|
|
||||||
self.on_change(input_, **self.kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def wrapped_on_cancel(self):
|
|
||||||
if not self.on_cancel == None:
|
|
||||||
self.on_cancel(**self.kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def invoke(self, window):
|
|
||||||
window.show_input_panel(self.caption, self.initial_text,
|
|
||||||
self.wrapped_on_done, self.wrapped_on_change,
|
|
||||||
self.wrapped_on_cancel)
|
|
||||||
|
|
||||||
|
|
||||||
class Entry:
|
|
||||||
def __init__(self, name, description="", action=None, kwargs={},
|
|
||||||
sub_menu=None, input=None):
|
|
||||||
self.name = name
|
|
||||||
self.description = description
|
|
||||||
self.action = action
|
|
||||||
self.kwargs = kwargs
|
|
||||||
self.sub_menu = sub_menu
|
|
||||||
self.input = input
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self.__dict__ == other.__dict__
|
|
||||||
|
|
||||||
|
|
||||||
class Menu:
|
|
||||||
def __init__(self, selected_index=-1, on_highlight=None, subtitles=True):
|
|
||||||
self.selected_index = selected_index
|
|
||||||
self.on_highlight = on_highlight
|
|
||||||
self.subtitles = subtitles
|
|
||||||
self.entries = {}
|
|
||||||
self.window = None
|
|
||||||
self.back = None
|
|
||||||
|
|
||||||
def invoke(self, window, back=None):
|
|
||||||
self.window = window
|
|
||||||
self.back = back
|
|
||||||
entries = self.menu_entries
|
|
||||||
if back:
|
|
||||||
entries.insert(0, ["Back",
|
|
||||||
"Back to previous menu"] if self.subtitles else ["Back"])
|
|
||||||
window.show_quick_panel(entries, self._action,
|
|
||||||
flags=0, selected_index=self.selected_index,
|
|
||||||
on_highlight=self.on_highlight)
|
|
||||||
|
|
||||||
def _action(self, entry_id):
|
|
||||||
if entry_id != -1:
|
|
||||||
if self.back:
|
|
||||||
if entry_id != 0:
|
|
||||||
entry = self.entries[entry_id - 1]
|
|
||||||
else:
|
|
||||||
self.back.invoke(self.window, back=self.back.back)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
entry = self.entries[entry_id]
|
|
||||||
if entry.action != None:
|
|
||||||
entry.action(**entry.kwargs)
|
|
||||||
if entry.input != None:
|
|
||||||
entry.input.invoke(self.window)
|
|
||||||
if type(entry.sub_menu) is FUNCTION:
|
|
||||||
entry.sub_menu(entry).invoke(self.window, back=self)
|
|
||||||
elif entry.sub_menu != None:
|
|
||||||
entry.sub_menu.invoke(self.window, back=self)
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def menu_entries(self):
|
|
||||||
entries = []
|
|
||||||
for entry_id in self.entries:
|
|
||||||
if self.subtitles:
|
|
||||||
entries.append([self.entries[entry_id].name, self.entries[entry_id].description])
|
|
||||||
else:
|
|
||||||
entries.append([self.entries[entry_id].name])
|
|
||||||
return entries
|
|
||||||
|
|
||||||
|
|
||||||
def __add__(self, other):
|
|
||||||
try:
|
|
||||||
self.add(other)
|
|
||||||
except TypeError:
|
|
||||||
return NotImplemented
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
def __sub__(self, other):
|
|
||||||
try:
|
|
||||||
self.remove(other)
|
|
||||||
except TypeError:
|
|
||||||
return NotImplemented
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
def add(self, entry):
|
|
||||||
if entry.__class__ == Entry:
|
|
||||||
if len(self.entries) > 0:
|
|
||||||
entry_id = tuple(self.entries.keys())[-1] + 1
|
|
||||||
else:
|
|
||||||
entry_id = 0
|
|
||||||
self.entries[entry_id] = entry
|
|
||||||
else:
|
|
||||||
raise TypeError("invalid type supplied")
|
|
||||||
|
|
||||||
|
|
||||||
def remove(self, entry):
|
|
||||||
if entry.__class__ == Entry:
|
|
||||||
if entry in self.entries.values():
|
|
||||||
found_entry_id = None
|
|
||||||
for entry_id in self.entries:
|
|
||||||
if self.entries[entry_id] == entry:
|
|
||||||
found_entry_id = entry_id
|
|
||||||
if found_entry_id != None:
|
|
||||||
del self.entries[entry_id]
|
|
||||||
else:
|
|
||||||
raise TypeError("invalid type supplied")
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self.entries = {}
|
|
|
@ -1,756 +0,0 @@
|
||||||
from sys import path
|
|
||||||
import os
|
|
||||||
from time import strftime
|
|
||||||
from functools import partial
|
|
||||||
import re
|
|
||||||
|
|
||||||
fl0w_path = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
shared_path = os.path.dirname(os.path.realpath(__file__)) + "/Shared/"
|
|
||||||
if fl0w_path not in path:
|
|
||||||
path.append(fl0w_path)
|
|
||||||
if shared_path not in path:
|
|
||||||
path.append(shared_path)
|
|
||||||
|
|
||||||
|
|
||||||
import sublime
|
|
||||||
import sublime_plugin
|
|
||||||
|
|
||||||
from Highway import Client, Route, Pipe, DummyPipe
|
|
||||||
from Utils import get_hostname, get_ip_from_url
|
|
||||||
|
|
||||||
import behem0th
|
|
||||||
|
|
||||||
from SublimeMenu import *
|
|
||||||
import Logging
|
|
||||||
|
|
||||||
import webbrowser
|
|
||||||
import threading
|
|
||||||
from time import sleep
|
|
||||||
import os
|
|
||||||
|
|
||||||
CHANNEL = 1
|
|
||||||
FL0W_STATUS = "fl0w"
|
|
||||||
|
|
||||||
def plugin_unloaded():
|
|
||||||
for window in windows:
|
|
||||||
if hasattr(window, "fl0w") and window.fl0w.connected:
|
|
||||||
window.fl0w.invoke_disconnect()
|
|
||||||
for sensor_type in ("analog", "digital"):
|
|
||||||
window.active_view().erase_phantoms(sensor_type)
|
|
||||||
|
|
||||||
|
|
||||||
PARENTHESES_REGEX = re.compile("\((.*?)\)")
|
|
||||||
STYLE_OPEN = "<body><style>code { color: var(--orangish); }</style><code>"
|
|
||||||
STYLE_CLOSE = "</code></body>"
|
|
||||||
|
|
||||||
ERROR_OPEN = "<body><style>code { color: var(--redish); }</style><code>"
|
|
||||||
ERROR_CLOSE = "</code></body>"
|
|
||||||
|
|
||||||
windows = []
|
|
||||||
views = []
|
|
||||||
sensor_phantoms = []
|
|
||||||
|
|
||||||
def set_status(status, window):
|
|
||||||
window.active_view().set_status(FL0W_STATUS,
|
|
||||||
"fl0w: %s" % status)
|
|
||||||
|
|
||||||
class Target:
|
|
||||||
def __init__(self, id_, name):
|
|
||||||
self.id = id_
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
|
|
||||||
class Fl0wClient(Client):
|
|
||||||
def setup(self, routes, fl0w, debug=False):
|
|
||||||
super().setup(routes, debug=debug)
|
|
||||||
self.fl0w = fl0w
|
|
||||||
|
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
self.fl0w.connected = True
|
|
||||||
if self.fl0w.debug:
|
|
||||||
Logging.info("Connection ready!")
|
|
||||||
# Enlist on editor channel
|
|
||||||
self.send({"channel" : CHANNEL, "name" : get_hostname()}, "subscribe")
|
|
||||||
# Subscribe to controller channel
|
|
||||||
self.send({"subscribe" : [2]}, "peers")
|
|
||||||
|
|
||||||
|
|
||||||
def closed(self, code, reason):
|
|
||||||
self.fl0w.invoke_disconnect()
|
|
||||||
if self.fl0w.debug:
|
|
||||||
Logging.info("Connection closed: %s (%s)" % (reason, code))
|
|
||||||
|
|
||||||
|
|
||||||
def peer_unavaliable(self, peer):
|
|
||||||
sublime.error_message("The specifed controller is not connected anymore.")
|
|
||||||
if self.fl0w.target.id == peer:
|
|
||||||
self.fl0w.target = None
|
|
||||||
|
|
||||||
|
|
||||||
class Info(Route):
|
|
||||||
def run(self, data, handler):
|
|
||||||
info = ""
|
|
||||||
for key in data:
|
|
||||||
info += "%s: %s\n" % (key.capitalize(), ", ".join(data[key]))
|
|
||||||
sublime.message_dialog(info)
|
|
||||||
handler.fl0w.meta.invoke(handler.fl0w.window, back=handler.fl0w.main_menu)
|
|
||||||
|
|
||||||
|
|
||||||
class Sensor(Pipe):
|
|
||||||
def run(self, data, peer, handler):
|
|
||||||
handler.fl0w.subscriptions_lock.acquire()
|
|
||||||
for sensor_phantom in handler.fl0w.subscriptions:
|
|
||||||
sensor_phantom.update_sensor_values(data)
|
|
||||||
handler.fl0w.subscriptions_lock.release()
|
|
||||||
|
|
||||||
|
|
||||||
class Peers(Route):
|
|
||||||
def start(self, handler):
|
|
||||||
self.selected_action_menu = None
|
|
||||||
|
|
||||||
def run(self, data, handler):
|
|
||||||
handler.fl0w.controller_menu.clear()
|
|
||||||
if handler.fl0w.target != None:
|
|
||||||
if not handler.fl0w.target.id in data:
|
|
||||||
handler.fl0w.target = None
|
|
||||||
for id_ in data:
|
|
||||||
action_menu = Menu()
|
|
||||||
power_menu = Menu()
|
|
||||||
utilities_menu = Menu()
|
|
||||||
action_menu.id_ = id_
|
|
||||||
action_menu.name = data[id_]["name"]
|
|
||||||
action_menu += Entry("Set Target",
|
|
||||||
"Set controller as target for program execution and sensor readouts",
|
|
||||||
action=partial(self.set_target,
|
|
||||||
handler, id_, data[id_]["name"]))
|
|
||||||
action_menu += Entry("Run program",
|
|
||||||
"Run a botball program on the controller",
|
|
||||||
action=partial(handler.pipe, None, "list_programs", id_))
|
|
||||||
action_menu += Entry("Stop programs",
|
|
||||||
"Stop all currently running botball programs",
|
|
||||||
action=partial(handler.pipe, None, "stop_programs", id_))
|
|
||||||
utilities_menu += Entry("Set Name",
|
|
||||||
"Sets the hostname of the selected controller",
|
|
||||||
action=partial(lambda handler, id_: Input("New Hostname:",
|
|
||||||
initial_text=data[id_]["name"],
|
|
||||||
on_done=lambda hostname: handler.pipe(
|
|
||||||
{"set" : hostname},
|
|
||||||
"hostname", id_)).invoke(handler.fl0w.window), handler, id_))
|
|
||||||
utilities_menu += Entry("Processes",
|
|
||||||
"Lists processes currently running on controller",
|
|
||||||
action=partial(handler.pipe, None, "processes", id_))
|
|
||||||
utilities_menu += Entry("Identify",
|
|
||||||
"Plays an identification sound on the controller.",
|
|
||||||
action=partial(handler.pipe, None, "identify", id_))
|
|
||||||
action_menu += Entry("Utilities", "Stuff you might need but probably won't",
|
|
||||||
sub_menu=utilities_menu)
|
|
||||||
power_menu += Entry("Shutdown",
|
|
||||||
"Shutdown the controller",
|
|
||||||
action=partial(handler.pipe, None, "shutdown", id_))
|
|
||||||
power_menu += Entry("Reboot",
|
|
||||||
"Reboot the controller",
|
|
||||||
action=partial(handler.pipe, None, "reboot", id_))
|
|
||||||
action_menu += Entry("Power", "Power related actions", sub_menu=power_menu)
|
|
||||||
action_menu.back = handler.fl0w.controller_menu
|
|
||||||
handler.fl0w.controller_menu += Entry(data[id_]["name"], id_, sub_menu=action_menu,
|
|
||||||
action=self.set_selected_action_menu,
|
|
||||||
kwargs={"selected_action_menu" : action_menu})
|
|
||||||
|
|
||||||
|
|
||||||
def set_target(self, handler, peer, name):
|
|
||||||
handler.fl0w.target = Target(peer, name)
|
|
||||||
if handler.fl0w.debug:
|
|
||||||
set_status("Target: %s" % peer, handler.fl0w.window)
|
|
||||||
|
|
||||||
|
|
||||||
def set_selected_action_menu(self, selected_action_menu):
|
|
||||||
self.selected_action_menu = selected_action_menu
|
|
||||||
|
|
||||||
|
|
||||||
class Processes(Pipe):
|
|
||||||
def run(self, data, peer, handler):
|
|
||||||
view = handler.fl0w.window.new_file()
|
|
||||||
view.set_name("Processes")
|
|
||||||
view.settings().set("draw_indent_guides", False)
|
|
||||||
for line in data:
|
|
||||||
view.run_command("append", {"characters": line + "\n"})
|
|
||||||
view.set_read_only(True)
|
|
||||||
|
|
||||||
|
|
||||||
class ListPrograms(Pipe):
|
|
||||||
def run(self, data, peer, handler):
|
|
||||||
program_menu = Menu(subtitles=False)
|
|
||||||
for program in data:
|
|
||||||
program_menu += Entry(program,
|
|
||||||
action=partial(self.run_program,
|
|
||||||
handler, handler.routes["peers"].selected_action_menu.id_,
|
|
||||||
program))
|
|
||||||
program_menu.invoke(handler.fl0w.window,
|
|
||||||
back=handler.routes["peers"].selected_action_menu)
|
|
||||||
|
|
||||||
def run_program(self, handler, id_, program):
|
|
||||||
handler.pipe(program, "run_program", id_)
|
|
||||||
|
|
||||||
|
|
||||||
class RunProgram(Pipe):
|
|
||||||
PROGRAM_NOT_FOUND = 1
|
|
||||||
|
|
||||||
def run(self, data, peer, handler):
|
|
||||||
if data == self.__class__.PROGRAM_NOT_FOUND:
|
|
||||||
sublime.error_message("Program not found.")
|
|
||||||
|
|
||||||
|
|
||||||
class StopPrograms(Pipe):
|
|
||||||
NO_PROGRAMS_RUNNING = 1
|
|
||||||
|
|
||||||
def run(self, data, peer, handler):
|
|
||||||
if data == self.__class__.NO_PROGRAMS_RUNNING:
|
|
||||||
sublime.error_message("No programs running.")
|
|
||||||
|
|
||||||
|
|
||||||
class StdStream(Pipe):
|
|
||||||
def start(self, handler):
|
|
||||||
self.output_panels = {}
|
|
||||||
|
|
||||||
self.lock = threading.RLock()
|
|
||||||
self.buffer = {}
|
|
||||||
|
|
||||||
self.handler = None
|
|
||||||
|
|
||||||
self.fetcher = self.Fetcher(self.buffer, self.write_to_panel,
|
|
||||||
self.lock)
|
|
||||||
self.fetcher.start()
|
|
||||||
|
|
||||||
|
|
||||||
def create_output_panel(self, window, peer):
|
|
||||||
view = window.create_output_panel(peer)
|
|
||||||
view.settings().set("draw_white_space", False)
|
|
||||||
view.settings().set("draw_indent_guides", False)
|
|
||||||
view.settings().set("gutter", False)
|
|
||||||
view.settings().set("line_numbers", False)
|
|
||||||
view.set_read_only(True)
|
|
||||||
return view
|
|
||||||
|
|
||||||
|
|
||||||
def run(self, data, peer, handler):
|
|
||||||
self.handler = handler
|
|
||||||
if type(data) is str:
|
|
||||||
self.lock.acquire()
|
|
||||||
# try/except is faster than an explicit if as long as the
|
|
||||||
# condition is not met
|
|
||||||
try:
|
|
||||||
self.buffer[peer].append(data)
|
|
||||||
except KeyError:
|
|
||||||
self.buffer[peer] = []
|
|
||||||
self.create_output_panel(handler.fl0w.window, peer)
|
|
||||||
self.output_panels[peer] = self.create_output_panel(handler.fl0w.window, peer)
|
|
||||||
self.buffer[peer].append(data)
|
|
||||||
self.lock.release()
|
|
||||||
# Meta info comes in so infrequently that the conditional logic would
|
|
||||||
# slow down the regular output streaming
|
|
||||||
elif type(data) is dict:
|
|
||||||
meta_text = ""
|
|
||||||
if "exit_code" in data:
|
|
||||||
meta_text += "Program finished with exit code: %d\n" % data["exit_code"]
|
|
||||||
self.lock.acquire()
|
|
||||||
# try/except is faster than an explicit if as long as the
|
|
||||||
# condition is not met
|
|
||||||
# function call is also slower
|
|
||||||
try:
|
|
||||||
self.buffer[peer].append(meta_text)
|
|
||||||
except KeyError:
|
|
||||||
self.buffer[peer] = []
|
|
||||||
self.create_output_panel(handler.fl0w.window, peer)
|
|
||||||
self.output_panels[peer] = self.create_output_panel(handler.fl0w.window, peer)
|
|
||||||
self.buffer[peer].append(meta_text)
|
|
||||||
self.lock.release()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def write_to_panel(self, text, peer):
|
|
||||||
self.output_panels[peer].set_read_only(False)
|
|
||||||
self.output_panels[peer].run_command("append", {"characters": text, "scroll_to_end" : True})
|
|
||||||
self.output_panels[peer].set_read_only(True)
|
|
||||||
self.handler.fl0w.window.run_command("show_panel", {"panel": "output.%s" % peer})
|
|
||||||
|
|
||||||
|
|
||||||
# Sublime gets quite overwhelmed when faced with typical
|
|
||||||
# "while (1) { printf(...)}" output.
|
|
||||||
# That's why instead of directly writing to the view all received text
|
|
||||||
# is bundled together after a fixed period of time.
|
|
||||||
class Fetcher(threading.Thread):
|
|
||||||
def __init__(self, buffer, write_to_panel, lock, push_rate=0.2):
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
self.buffer = buffer
|
|
||||||
self.write_to_panel = write_to_panel
|
|
||||||
self.lock = lock
|
|
||||||
self.push_rate = push_rate
|
|
||||||
self.daemon = True
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while True:
|
|
||||||
self.lock.acquire()
|
|
||||||
for peer in self.buffer:
|
|
||||||
if len(self.buffer[peer]) > 0:
|
|
||||||
self.write_to_panel(
|
|
||||||
"".join(self.buffer[peer]),
|
|
||||||
peer)
|
|
||||||
self.buffer[peer] = []
|
|
||||||
self.lock.release()
|
|
||||||
sleep(self.push_rate)
|
|
||||||
|
|
||||||
|
|
||||||
class Fl0w:
|
|
||||||
def __init__(self, window, debug=False):
|
|
||||||
self.settings = sublime.load_settings("fl0w.sublime-settings")
|
|
||||||
self.window = window
|
|
||||||
self.folder = window.folders()[0]
|
|
||||||
if self.folder != "/":
|
|
||||||
self.folder = self.folder + "/"
|
|
||||||
|
|
||||||
self.connected = False
|
|
||||||
|
|
||||||
self.subscriptions = {}
|
|
||||||
self.subscriptions_lock = threading.Lock()
|
|
||||||
self._combined_subscriptions = {"analog" : [], "digital" : []}
|
|
||||||
|
|
||||||
self._target = None
|
|
||||||
self._debug = debug
|
|
||||||
|
|
||||||
|
|
||||||
self.start_menu = Menu()
|
|
||||||
self.start_menu += Entry("Connect", "Connect to a fl0w server",
|
|
||||||
action=partial(Input("Address:Port (auto-connect nyi)",
|
|
||||||
initial_text=self.settings.get("address", "127.0.0.1:3077"),
|
|
||||||
on_done=self.invoke_connect).invoke, self.window))
|
|
||||||
self.start_menu += Entry("About", "Information about fl0w",
|
|
||||||
action=self.invoke_about)
|
|
||||||
|
|
||||||
self.debug_menu = Menu(subtitles=False)
|
|
||||||
self.debug_menu += Entry("On",
|
|
||||||
action=lambda: self.set_debug(True))
|
|
||||||
self.debug_menu += Entry("Off",
|
|
||||||
action=lambda: self.set_debug(False))
|
|
||||||
|
|
||||||
|
|
||||||
self.settings_menu = Menu()
|
|
||||||
self.settings_menu += Entry("Debug", "Toggle debug mode",
|
|
||||||
sub_menu=self.debug_menu)
|
|
||||||
|
|
||||||
|
|
||||||
self.meta = Menu()
|
|
||||||
self.meta += Entry("Info", "Server info",
|
|
||||||
action=lambda: self.ws.send(None, "info"))
|
|
||||||
self.meta_entry = Entry("Meta", "Debug information about fl0w",
|
|
||||||
sub_menu=self.meta)
|
|
||||||
if self.debug:
|
|
||||||
self.main_menu += self.meta_entry
|
|
||||||
|
|
||||||
|
|
||||||
self.main_menu = Menu()
|
|
||||||
self.controller_menu = Menu()
|
|
||||||
self.main_menu += Entry("Controllers", "All connected controllers",
|
|
||||||
sub_menu=self.controller_menu)
|
|
||||||
self.main_menu += Entry("Settings", "General purpose settings",
|
|
||||||
sub_menu=self.settings_menu)
|
|
||||||
self.main_menu += Entry("Disconnect", "Disconnect from server",
|
|
||||||
action=self.invoke_disconnect)
|
|
||||||
|
|
||||||
self.sync_client = behem0th.Client(path=self.folder, verbose_log=True)
|
|
||||||
|
|
||||||
# Patch all sensor phantom that have been created before a fl0w instance
|
|
||||||
# was attached to the window
|
|
||||||
for sensor_phantom in sensor_phantoms:
|
|
||||||
if sensor_phantom.window.id() == self.window.id():
|
|
||||||
sensor_phantom.fl0w = self
|
|
||||||
if self.debug:
|
|
||||||
Logging.info("Patched sensor phantom '%s'" % str(sensor_phatom))
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def target(self):
|
|
||||||
return self._target
|
|
||||||
|
|
||||||
|
|
||||||
@target.setter
|
|
||||||
def target(self, target):
|
|
||||||
if self.target != None:
|
|
||||||
self.ws.pipe("unsubscribe", "sensor", self.target.id)
|
|
||||||
self._target = target
|
|
||||||
if target != None:
|
|
||||||
set_status("Target: %s (%s)" % (target.name, target.id), self.window)
|
|
||||||
if self.combined_subscriptions != {"analog" : [], "digital" : []}:
|
|
||||||
self.ws.pipe({"subscribe" : self.combined_subscriptions}, "sensor", target.id)
|
|
||||||
else:
|
|
||||||
set_status("The target has become unavaliable.", self.window)
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def debug(self):
|
|
||||||
return self._debug
|
|
||||||
|
|
||||||
|
|
||||||
def set_debug(self, debug):
|
|
||||||
self.debug = debug
|
|
||||||
|
|
||||||
|
|
||||||
@debug.setter
|
|
||||||
def debug(self, debug):
|
|
||||||
if debug:
|
|
||||||
self._debug = True
|
|
||||||
if not self.meta_entry in self.main_menu.entries.values():
|
|
||||||
self.main_menu += self.meta_entry
|
|
||||||
else:
|
|
||||||
self._debug = False
|
|
||||||
self.main_menu -= self.meta_entry
|
|
||||||
set_status("Debug set to %s" % self._debug, self.window)
|
|
||||||
|
|
||||||
|
|
||||||
# Could be simplified because only one view can be active at any time.
|
|
||||||
# This would definetly lead to some major performace improvements on
|
|
||||||
# view switching and less unnecessary unsubscribes.
|
|
||||||
|
|
||||||
# On the other hand it might be a good idea to leave it in and provide
|
|
||||||
# an option to disable aggressive unsubscribes
|
|
||||||
@property
|
|
||||||
def combined_subscriptions(self):
|
|
||||||
return self._combined_subscriptions
|
|
||||||
|
|
||||||
|
|
||||||
@combined_subscriptions.setter
|
|
||||||
def combined_subscriptions(self, combined_subscriptions_):
|
|
||||||
if self.combined_subscriptions != combined_subscriptions_:
|
|
||||||
self._combined_subscriptions = combined_subscriptions_
|
|
||||||
if self.connected and self.target != None:
|
|
||||||
self.ws.pipe("unsubscribe", "sensor", self.target.id)
|
|
||||||
if combined_subscriptions_ != {"analog" : [], "digital" : []}:
|
|
||||||
self.ws.pipe({"subscribe" : combined_subscriptions_}, "sensor",
|
|
||||||
self.target.id)
|
|
||||||
|
|
||||||
|
|
||||||
def subscribe(self, sensor_phatom, subscriptions):
|
|
||||||
self.subscriptions_lock.acquire()
|
|
||||||
self.subscriptions[sensor_phatom] = subscriptions
|
|
||||||
self.subscriptions_lock.release()
|
|
||||||
self.make_subscriptions()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def unsubscribe(self, sensor_phantom):
|
|
||||||
if sensor_phantom in self.subscriptions:
|
|
||||||
"""
|
|
||||||
print("Lock will be aquired.")
|
|
||||||
self.subscriptions_lock.acquire()
|
|
||||||
print("Lock was aquired.")
|
|
||||||
del self.subscriptions[sensor_phantom]
|
|
||||||
print("Lock will be released.")
|
|
||||||
self.subscriptions_lock.release()
|
|
||||||
print("Lock was released.")
|
|
||||||
"""
|
|
||||||
# Temporary solution, locking caused sublime to freeze
|
|
||||||
# Could cause problems if lots (> 100) views are open.
|
|
||||||
self.subscriptions[sensor_phantom] = {"analog" : [], "digital" : []}
|
|
||||||
self.make_subscriptions()
|
|
||||||
|
|
||||||
|
|
||||||
def make_subscriptions(self):
|
|
||||||
combined_subscriptions = {"analog" : [], "digital" : []}
|
|
||||||
for sensor_phantom in self.subscriptions:
|
|
||||||
for sensor_type in ("analog", "digital"):
|
|
||||||
combined_subscriptions[sensor_type] = list(
|
|
||||||
set(combined_subscriptions[sensor_type]) |
|
|
||||||
set(self.subscriptions[sensor_phantom][sensor_type])
|
|
||||||
)
|
|
||||||
self.combined_subscriptions = combined_subscriptions
|
|
||||||
|
|
||||||
|
|
||||||
def run_program(self, path):
|
|
||||||
if self.connected and self.target != None:
|
|
||||||
relpath = os.path.relpath(path, self.folder)
|
|
||||||
if os.path.isfile(self.folder + relpath):
|
|
||||||
if self.debug:
|
|
||||||
Logging.info("Running program '%s'" % relpath)
|
|
||||||
self.ws.pipe(relpath.rstrip(".c"), "run_program", self.target.id)
|
|
||||||
|
|
||||||
|
|
||||||
def stop_programs(self):
|
|
||||||
if self.connected and self.target != None:
|
|
||||||
self.ws.pipe(None, "stop_programs", self.target.id)
|
|
||||||
|
|
||||||
|
|
||||||
def invoke_start_menu(self):
|
|
||||||
self.start_menu.invoke(self.window)
|
|
||||||
|
|
||||||
|
|
||||||
def invoke_main_menu(self):
|
|
||||||
self.main_menu.invoke(self.window)
|
|
||||||
|
|
||||||
|
|
||||||
def invoke_about(self):
|
|
||||||
if sublime.ok_cancel_dialog("fl0w by @robot0nfire", "robot0nfire.com"):
|
|
||||||
webbrowser.open("http://robot0nfire.com")
|
|
||||||
|
|
||||||
|
|
||||||
def connect(self, address):
|
|
||||||
try:
|
|
||||||
self.ws = Fl0wClient(address)
|
|
||||||
self.ws.setup({"info" : Fl0wClient.Info(), "peers" : Fl0wClient.Peers(),
|
|
||||||
"processes" : Fl0wClient.Processes(),
|
|
||||||
"list_programs" : Fl0wClient.ListPrograms(), "sensor" : Fl0wClient.Sensor(),
|
|
||||||
"std_stream" : Fl0wClient.StdStream(), "run_program" : Fl0wClient.RunProgram(),
|
|
||||||
"stop_programs" : Fl0wClient.StopPrograms()},
|
|
||||||
self, debug=True)
|
|
||||||
self.ws.connect()
|
|
||||||
sublime.set_timeout_async(self.ws.run_forever, 0)
|
|
||||||
set_status("Connection opened '%s'" % self.folder, self.window)
|
|
||||||
self.connected = True
|
|
||||||
self.settings.set("address", address)
|
|
||||||
self.sync_client.connect(get_ip_from_url(address))
|
|
||||||
|
|
||||||
except (OSError, ConnectionRefusedError) as e:
|
|
||||||
sublime.error_message("Error during connection creation:\n %s" % str(e))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def invoke_connect(self, address):
|
|
||||||
# Will be removed once autoconnect works
|
|
||||||
self.connect("ws://%s" % address)
|
|
||||||
|
|
||||||
|
|
||||||
def invoke_disconnect(self):
|
|
||||||
if self.connected:
|
|
||||||
for sensor_phantom in sensor_phantoms:
|
|
||||||
if sensor_phantom.window.id() == self.window.id():
|
|
||||||
sensor_phantom.enabled = False
|
|
||||||
self.target = None
|
|
||||||
self.ws.close()
|
|
||||||
set_status("Connection closed '%s'" % self.folder, self.window)
|
|
||||||
self.connected = False
|
|
||||||
|
|
||||||
self.sync_client.close()
|
|
||||||
|
|
||||||
|
|
||||||
class Fl0wCommand(sublime_plugin.WindowCommand):
|
|
||||||
def run(self):
|
|
||||||
valid_window_setup = True
|
|
||||||
folder_count = len(self.window.folders())
|
|
||||||
if folder_count > 1:
|
|
||||||
sublime.error_message("Only one open folder per window is allowed.")
|
|
||||||
valid_window_setup = False
|
|
||||||
elif folder_count == 0:
|
|
||||||
sublime.error_message("No folder open in window.")
|
|
||||||
valid_window_setup = False
|
|
||||||
if valid_window_setup:
|
|
||||||
if not hasattr(self.window, "fl0w"):
|
|
||||||
folder = self.window.folders()[0]
|
|
||||||
files = os.listdir(folder)
|
|
||||||
if not ".no-fl0w" in files:
|
|
||||||
if not ".fl0w" in files:
|
|
||||||
open(folder + "/.fl0w", 'a').close()
|
|
||||||
self.window.fl0w = Fl0w(self.window)
|
|
||||||
windows.append(self.window)
|
|
||||||
self.window.fl0w.start_menu.invoke(self.window)
|
|
||||||
else:
|
|
||||||
self.window.fl0w = Fl0w(self.window)
|
|
||||||
windows.append(self.window)
|
|
||||||
self.window.fl0w.start_menu.invoke(self.window)
|
|
||||||
else:
|
|
||||||
sublime.error_message("fl0w can't be opened in your current directory (.no-fl0w file exists)")
|
|
||||||
else:
|
|
||||||
if not self.window.fl0w.connected:
|
|
||||||
self.window.fl0w.invoke_start_menu()
|
|
||||||
else:
|
|
||||||
self.window.fl0w.invoke_main_menu()
|
|
||||||
else:
|
|
||||||
if hasattr(self.window, "fl0w"):
|
|
||||||
sublime.error_message("Window setup was invalidated (Don't close or open any additional folders in a fl0w window)")
|
|
||||||
self.window.fl0w.invoke_disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
class RunCommand(sublime_plugin.WindowCommand):
|
|
||||||
def run(self):
|
|
||||||
if hasattr(self.window, "fl0w"):
|
|
||||||
if self.window.fl0w.connected:
|
|
||||||
if self.window.fl0w.target == None:
|
|
||||||
sublime.error_message("A target controller has to be set to "
|
|
||||||
"run programs.")
|
|
||||||
else:
|
|
||||||
file_name = self.window.active_view().file_name()
|
|
||||||
if file_name != None and file_name.endswith(".c"):
|
|
||||||
self.window.fl0w.run_program(file_name)
|
|
||||||
else:
|
|
||||||
sublime.error_message("fl0w is not connected.")
|
|
||||||
else:
|
|
||||||
sublime.error_message("fl0w is not running in your current window.")
|
|
||||||
|
|
||||||
class StopCommand(sublime_plugin.WindowCommand):
|
|
||||||
def run(self):
|
|
||||||
if hasattr(self.window, "fl0w"):
|
|
||||||
if self.window.fl0w.connected:
|
|
||||||
if self.window.fl0w.target == None:
|
|
||||||
sublime.error_message("A target controller has to be set to "
|
|
||||||
"stop programs.")
|
|
||||||
else:
|
|
||||||
self.window.fl0w.stop_programs()
|
|
||||||
else:
|
|
||||||
sublime.error_message("fl0w is not connected.")
|
|
||||||
else:
|
|
||||||
sublime.error_message("fl0w is not running in your current window.")
|
|
||||||
|
|
||||||
|
|
||||||
class SensorCommand(sublime_plugin.WindowCommand):
|
|
||||||
def run(self):
|
|
||||||
if hasattr(self.window, "fl0w"):
|
|
||||||
if self.window.fl0w.connected:
|
|
||||||
if self.window.fl0w.target == None:
|
|
||||||
sublime.error_message("A target controller has to be set to "
|
|
||||||
"enable inline sensor readouts.")
|
|
||||||
else:
|
|
||||||
view_id = self.window.active_view().id()
|
|
||||||
for view in views:
|
|
||||||
if view.id() == view_id:
|
|
||||||
view.sensor_phantom.enabled = not view.sensor_phantom.enabled
|
|
||||||
view_file_name = view.file_name()
|
|
||||||
if view_file_name == None:
|
|
||||||
view_file_name = "untitled"
|
|
||||||
set_status("%s sensor phantoms for '%s'." % (
|
|
||||||
"Enabled" if view.sensor_phantom.enabled else "Disabled",
|
|
||||||
view_file_name),
|
|
||||||
self.window)
|
|
||||||
else:
|
|
||||||
sublime.error_message("fl0w is not connected.")
|
|
||||||
else:
|
|
||||||
sublime.error_message("fl0w is not running in your current window.")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SensorPhantom(sublime_plugin.ViewEventListener):
|
|
||||||
def __init__(self, view):
|
|
||||||
self.view = view
|
|
||||||
self.view.sensor_phantom = self
|
|
||||||
if not view in views:
|
|
||||||
views.append(view)
|
|
||||||
self.window = view.window()
|
|
||||||
|
|
||||||
# Is patched by the fl0w instance that is in control of the same window
|
|
||||||
self.fl0w = None
|
|
||||||
self._enabled = False
|
|
||||||
|
|
||||||
self.previously_enabled = False
|
|
||||||
|
|
||||||
self._matches = {"analog" : [], "digital" : []}
|
|
||||||
|
|
||||||
self.timeout_scheduled = False
|
|
||||||
self.needs_update = False
|
|
||||||
|
|
||||||
for window in windows:
|
|
||||||
if hasattr(window, "fl0w"):
|
|
||||||
self.fl0w = window.fl0w
|
|
||||||
if not self in sensor_phantoms:
|
|
||||||
sensor_phantoms.append(self)
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def enabled(self):
|
|
||||||
return self._enabled
|
|
||||||
|
|
||||||
|
|
||||||
@enabled.setter
|
|
||||||
def enabled(self, enabled_):
|
|
||||||
if enabled_:
|
|
||||||
if self.fl0w != None:
|
|
||||||
self.find_matches()
|
|
||||||
self.fl0w.subscribe(self, self.subscriptions)
|
|
||||||
self._enabled = True
|
|
||||||
else:
|
|
||||||
if self.fl0w != None:
|
|
||||||
self.fl0w.unsubscribe(self)
|
|
||||||
for sensor_type in ("analog", "digital"):
|
|
||||||
self.view.erase_phantoms(sensor_type)
|
|
||||||
self._enabled = False
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def matches(self):
|
|
||||||
return self._matches
|
|
||||||
|
|
||||||
@matches.setter
|
|
||||||
def matches(self, matches_):
|
|
||||||
if not matches_ == self.matches:
|
|
||||||
self._matches = matches_
|
|
||||||
self.fl0w.subscribe(self, self.subscriptions)
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def subscriptions(self):
|
|
||||||
subscriptions_ = {"analog" : [], "digital" : []}
|
|
||||||
for sensor_type in ("analog", "digital"):
|
|
||||||
subscriptions_[sensor_type] = [sensor[0] for sensor in self.matches[sensor_type]]
|
|
||||||
return subscriptions_
|
|
||||||
|
|
||||||
|
|
||||||
def find_matches(self):
|
|
||||||
matches = {"analog" : [], "digital" : []}
|
|
||||||
# Don't do any calculations on 1MB or larger files
|
|
||||||
if self.view.size() < 2**20:
|
|
||||||
for method_name in ("analog", "digital"):
|
|
||||||
candidates = self.view.find_all("%s\(\d*\)" % method_name)
|
|
||||||
for candidate in candidates:
|
|
||||||
line = self.view.substr(candidate)
|
|
||||||
port_candidates = re.findall(PARENTHESES_REGEX, line)
|
|
||||||
if len(port_candidates) == 1:
|
|
||||||
if port_candidates[0].isnumeric():
|
|
||||||
matches[method_name].append(
|
|
||||||
(
|
|
||||||
int(port_candidates[0]),
|
|
||||||
sublime.Region(self.view.line(candidate.a).b)
|
|
||||||
))
|
|
||||||
self.matches = matches
|
|
||||||
|
|
||||||
# Called by fl0w instance
|
|
||||||
def update_sensor_values(self, readouts):
|
|
||||||
for sensor_type in ("analog", "digital"):
|
|
||||||
self.view.erase_phantoms(sensor_type)
|
|
||||||
for match in self.matches[sensor_type]:
|
|
||||||
try:
|
|
||||||
self.view.add_phantom(sensor_type, match[1],
|
|
||||||
STYLE_OPEN + str(readouts[sensor_type][str(match[0])]) + STYLE_CLOSE,
|
|
||||||
sublime.LAYOUT_INLINE)
|
|
||||||
except KeyError:
|
|
||||||
self.view.add_phantom(sensor_type, match[1],
|
|
||||||
ERROR_OPEN + "!" + ERROR_CLOSE,
|
|
||||||
sublime.LAYOUT_INLINE)
|
|
||||||
|
|
||||||
|
|
||||||
def handle_timeout(self):
|
|
||||||
self.timeout_scheduled = False
|
|
||||||
if self.needs_update:
|
|
||||||
self.needs_update = False
|
|
||||||
self.find_matches()
|
|
||||||
|
|
||||||
|
|
||||||
def on_modified(self):
|
|
||||||
if self.enabled:
|
|
||||||
if self.timeout_scheduled:
|
|
||||||
self.needs_update = True
|
|
||||||
else:
|
|
||||||
sublime.set_timeout(lambda: self.handle_timeout(), 500)
|
|
||||||
self.find_matches()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def on_deactivated(self):
|
|
||||||
self.previously_enabled = self.enabled
|
|
||||||
if self.enabled:
|
|
||||||
self.enabled = False
|
|
||||||
|
|
||||||
def on_activated(self):
|
|
||||||
if not self.enabled and self.previously_enabled:
|
|
||||||
self.enabled = True
|
|
||||||
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
self.enabled = False
|
|
||||||
if self in sensor_phantoms:
|
|
||||||
del sensor_phantoms[sensor_phantoms.index(self)]
|
|
|
@ -1,6 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"caption": "fl0w: Menu",
|
|
||||||
"command": "fl0w"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"server_address": "", // Last server address
|
|
||||||
"compression_level": 2
|
|
||||||
}
|
|
Reference in a new issue