From fcf1bdd3018d620982d9e31e2a8ad35a33dbfb5e Mon Sep 17 00:00:00 2001 From: David Diaz Date: Wed, 25 Jul 2018 02:25:31 -0600 Subject: [PATCH] Added a loading spinner. It lets you know when stuff is going on. --- beau.py | 12 ++++- messages/install.txt | 9 ++-- send_self.py | 67 +++++++++++++++++++++++ status_loops.py | 123 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 send_self.py create mode 100644 status_loops.py diff --git a/beau.py b/beau.py index e54e238..6c765e6 100644 --- a/beau.py +++ b/beau.py @@ -1,6 +1,9 @@ import json import platform import sublime_plugin +import subprocess +import sublime +from .status_loops import loop_status_msg from threading import Thread from http.client import responses from sublime import load_settings, active_window @@ -21,15 +24,18 @@ class BeauCommand(sublime_plugin.TextCommand): active_view = None scope = None folders = [] + stop = None def inThread(self, command, onComplete, cwd=None): def thread(command, onComplete): try: + self.stop = loop_status_msg([' ⣾', ' ⣽', ' ⣻', ' ⢿', ' ⡿', ' ⣟', ' ⣯', ' ⣷'], .1) proc = check_output(command, shell=is_windows, stderr=subprocess.STDOUT, cwd=cwd) onComplete(proc) return except subprocess.CalledProcessError as e: - active_window().status_message('Beau Command Failed. Open the console for more info.') + self.stop() + sublime.set_timeout_async(lambda: active_window().status_message('Beau Command Failed. Open the console for more info.'), 1000) print(e.output) thread = Thread(target=thread, args=(command, onComplete)) @@ -55,6 +61,8 @@ class BeauCommand(sublime_plugin.TextCommand): ) def listFetched(self, list): + self.stop(True) + requests = [] self.requests[:] = [] for line in list.splitlines(): @@ -74,6 +82,8 @@ class BeauCommand(sublime_plugin.TextCommand): active_window().status_message('Running: ' + alias) def handleResult(result): + self.stop(True) + response = [] for line in result.splitlines(): response.append(line.rstrip()) diff --git a/messages/install.txt b/messages/install.txt index 72e54d5..ed71dc9 100644 --- a/messages/install.txt +++ b/messages/install.txt @@ -1,15 +1,16 @@ -# Beau +Installation -## Installation Before beginning you need to have installed beau, you can find out how to do so here: https://github.com/seich/beau. -## Setup +Setup + The first thing you should do now if open up the settings file (Preferences > Package Settings > Beau > Settings - User) and set up your cli_path. It should be pointing to the location of your beau installation. -## Usage +Usage + Once that's done, you can start use beau without leaving sublime. To use this plugin, open up a beau config file and open the commands palette (ctrl + shift + p). Type Beau Request and select the request you want to make. A new file should open showing you the diff --git a/send_self.py b/send_self.py new file mode 100644 index 0000000..3c3e514 --- /dev/null +++ b/send_self.py @@ -0,0 +1,67 @@ +""" +The MIT License (MIT) +Copyright (c) 2015 Clay Sweetser +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +from weakref import proxy, WeakKeyDictionary +from sys import version_info +from functools import wraps +import sublime + +def get_next_method(generator_instance): + if version_info[0] >= 3: + return generator_instance.__next__ + else: + return generator_instance.next + + +def send_self(use_proxy): + """ A decorator which sends a generator a reference to itself via the first + 'yield' used. + Useful for creating generators that can leverage callback-based functions + in a linear style, by passing their 'send' method as callbacks. + Note that by default, the generator instance reference sent is a weakly + referenced proxy. To override this behavior, pass `False` or + `use_proxy=False` as the first argument to the decorator. + """ + _use_proxy = True + + # We either directly call this, or return it, to be called by python's + # decorator mechanism. + def _send_self(func): + @wraps(func) + def send_self_wrapper(*args, **kwargs): + generator = func(*args, **kwargs) + generator.send(None) + if _use_proxy: + generator.send(proxy(generator)) + else: + generator.send(generator) + + return send_self_wrapper + + # If the argument is a callable, we've been used without being directly + # passed an arguement by the user, and thus should call _send_self directly + if callable(use_proxy): + # No arguments, this is the decorator + return _send_self(use_proxy) + else: + # Someone has used @send_self(bool), and thus we need to return + # _send_self to be called indirectly. + _use_proxy = use_proxy + return _send_self diff --git a/status_loops.py b/status_loops.py new file mode 100644 index 0000000..3a9b74d --- /dev/null +++ b/status_loops.py @@ -0,0 +1,123 @@ + +""" +The MIT License (MIT) +Copyright (c) 2015 Clay Sweetser +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" +from .send_self import send_self, get_next_method +import sublime + +def loop_status_msg(frames, speed, view=None, key=''): + """ Creates and runs a generator which continually sets the status + text to a series of strings. Returns a function which, when called, + stops the generator. + Useful for creating 'animations' in the status bar. + Parameters: + `frames`: A sequence of strings displayed in order on the status bar + `speed`: Delay between frame shifts, in seconds + `view`: View to set the status on. If not provided, then + sublime.status_message is used. + `key`: Key used when setting the status on a view. Ignored if no + view is given. + To stop the loop, the returned function must be called with no arguments, + or a single argument for which `bool(arg) == true`. As a special condition, + if the first argument is a callable for which `bool(arg) == True`, then + the argument will be called after the last animation loop has finished. + If for the the given argument, `bool(arg) == False`, nothing will + happen. + """ + flag = _FlagObject() + flag.flag = False + + @send_self + def loop_status_generator(): + self = yield + + # Get the correct status function + set_timeout = sublime.set_timeout + if view is None: + set_status = sublime.status_message + else: + set_status = lambda f: view.set_status(key, f) + + # Main loop + while not flag.flag: + for frame in frames: + set_status(frame) + yield set_timeout(get_next_method(self), int(speed * 1000)) + if callable(flag.flag): + flag.flag() + set_status('') + yield + + def stop_status_loop(callback=True): + flag.flag = callback + + sublime.set_timeout(loop_status_generator, 0) + return stop_status_loop + + +def static_status_msg(frame, speed=1): + """ Creates and runs a generator which displays an updatable message in + the current window. + Parameters: + `frame`: Initial message text + `speed`: Update speed, in seconds. Faster speed means faster message + update, but more CPU usage. Slower update speed means less + CPU usage, but slower message update. + To update the loop, call the returned function with the new message. + To stop displaying the message, call the returned function with 'true' or + a callable as the second parameter. + """ + flag = _FlagObject() + flag.flag = False + flag.frame = frame + + @send_self + def static_status_generator(): + self = yield + + # Get the correct status function + set_timeout = sublime.set_timeout + set_status = sublime.status_message + + # Main loop + while not flag.flag: + set_status(flag.frame) + yield set_timeout(get_next_method(self), int(speed * 1000)) + if callable(flag.flag): + flag.flag() + yield + + def update_status_loop(message, stop=False): + flag.flag = stop + flag.frame = message + + sublime.set_timeout(static_status_generator, 0) + return update_status_loop + + +class _FlagObject(object): + + """ + Used with loop_status_msg to signal when a status message loop should end. + """ + __slots__ = ['flag', 'frame'] + + def __init__(self): + self.flag = False + self.frame = None