From cb3f9dde3f7e7ffe05d02239b4ffc5740e6bb7aa Mon Sep 17 00:00:00 2001 From: GChristensen Date: Wed, 13 Jul 2022 22:25:33 +0400 Subject: [PATCH] sped up (un)learn as open commands fixed web search commands fixed delayed Shift key state --- changelog.md | 6 + commands.md | 193 ------------------ enso/commands/idgen.py | 2 +- enso/commands/open.py | 23 ++- enso/commands/web_search.py | 43 ++-- .../platform/win32/selection/_ContextUtils.py | 1 + .../platform/win32/shortcuts/Shortcuts.py | 41 ++-- enso/enso/strings.py | 2 +- enso/enso/webui/API.html | 14 +- enso/enso/webui/tutorial.html | 40 ++-- enso/scripts/run_enso.py | 4 +- readme.md | 9 +- setup.nsi | 2 +- 13 files changed, 116 insertions(+), 264 deletions(-) delete mode 100644 commands.md diff --git a/changelog.md b/changelog.md index 9ee0ee2c..a5eed4e4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,9 @@ +##### 14.07.2022 (v.0.9.1) + +* Sped up `learn as open` and `unlearn as open` commands. +* Fixed delayed Shift key state when localized input is enabled. +* Fixed web-search commands. + ##### 24.12.2021 (v.0.9.0) * Added experimental support of internationalized command input. Non-latin characters such as спасибо are allowed when the current input language is not English. Set LOCALIZED_INPUT = False in the settings configuration block to disable diff --git a/commands.md b/commands.md deleted file mode 100644 index 397542af..00000000 --- a/commands.md +++ /dev/null @@ -1,193 +0,0 @@ -# Creating Enso Commands - -To create a command for the portable distribution of Enso put the code of your command -into a python file under the ``commands`` directory at the root of the distribution. - -Hello World: Displaying Transparent Messages --------------------------------------------- - -A simple command called "hello world" can be created by entering the -following into your command source code file: -```python - def cmd_hello_world(ensoapi): - ensoapi.display_message("Hello World!") -``` -As soon as the the file is saved, the Enso quasimode can -be entered and the command used: Enso scans this file and its -dependencies whenever the quasimode is entered, and if the contents -have changed, Enso reloads them, so there is never a need to restart -Enso itself when developing commands. - -From the source code of the command, a number of things can be -observed: - - * A command is a function that starts with the prefix ``cmd_``. - * The name of a command is everything following the prefix, - with underscores converted to spaces. - * A command takes an ``ensoapi`` object as a parameter, which can - be used to access Enso-specific functionality. - -You may want to take the time to play around with the "hello world" -example; try raising an exception in the function body; try adding a -syntax error in the file and see what happens. It should be apparent -that such human errors have been accounted for and are handled in a -way that is considerate of one's frailties, allowing the programmer to -write and test code with minimal interruptions to their train of -thought. - -One may wonder why the ``ensoapi`` object has to be explicitly - passed-in rather than being imported. The reasons for this are - manifold: firstly, importing a specific module, e.g. ``enso.api``, - would tie the command to a particular implementation of the Enso - API. Yet it should be possible for the command to run in different - kinds of contexts - for instance, one where Enso itself is in a - separate process or even on a separate computer, and ``ensoapi`` is - just a proxy object. Secondly, explicitly passing in the object - makes the unit testing of commands easier. - -Adding Help Text ----------------- - -When using the "hello world" command, you may notice that the help -text displayed above the command entry display isn't very helpful. -You can set it to something nicer by adding a docstring to your -command function, like so: -```python - def cmd_hello_world(ensoapi): - "Displays a friendly greeting." - - ensoapi.display_message("Hello World!") -``` -If you add anything past a first line in the docstring, it will be -rendered as HTML in the documentation for the command when the user -runs the "help" command: -```python - def cmd_hello_world(ensoapi): - """ - Displays a friendly greeting. - - This command can be used in any application, at any time, - providing you with a hearty salutation at a moment's notice. - """ - - ensoapi.display_message("Hello World!") -``` -Interacting with The Current Selection --------------------------------------- - -To obtain the current selection, use ``ensoapi.get_selection()``. -This method returns a *selection dictionary*, or seldict for short. A -seldict is simply a dictionary that maps a data format identifier to -selection data in that format. - -Some valid data formats in a seldict are: - - * ``text``: Plain unicode text of the current selection. - * ``files``: A list of filenames representing the current selection. - -Setting the current selection works similarly: just pass -``ensoapi.set_selection()`` a seldict containing the selection data to -set. - -The following is an implementation of an "upper case" command that -converts the user's current selection to upper case: -```python - def cmd_upper_case(ensoapi): - text = ensoapi.get_selection().get("text") - if text: - ensoapi.set_selection({"text" : text.upper()}) - else: - ensoapi.display_message("No selection!") -``` -Command Arguments ------------------ - -It's possible for a command to take arbitrary arguments; an example of -this is the "google" command, which allows you to optionally specify a -search term following the command name. To create a command like -this, just add a parameter to the command function: -```python - def cmd_boogle(ensoapi, query): - ensoapi.display_message("You said: %s" % query) -``` -Unless you specify a default for your argument, however, a friendly -error message will be displayed when the user runs the command without -specifying one. If you don't want this to be the case, just add a -default argument to the command function: -```python - def cmd_boogle(ensoapi, query="pants"): - ensoapi.display_message("You said: %s" % query) -``` -If you want the argument to be bounded to a particular set of options, -you can specify them by attaching a ``valid_args`` property to your -command function. For instance: -```python - def cmd_vote_for(ensoapi, candidate): - ensoapi.display_message("You voted for: %s" % candidate) - cmd_vote_for.valid_args = ["barack obama", "john mccain"] -``` -Prolonged Execution -------------------- - -It's expected that some commands, such as ones that need to fetch -resources from the internet, may take some time to execute. If this -is the case, a command function may use Python's ``yield`` statement -to return control back to Enso when it needs to wait for something to -finish. For example: -```python - def cmd_rest_awhile(ensoapi): - import time, threading - - def do_something(): - time.sleep(3) - t = threading.Thread(target = do_something) - t.start() - ensoapi.display_message("Please wait...") - while t.isAlive(): - yield - ensoapi.display_message("Done!") -``` -Returning control back to Enso is highly encouraged - without it, your -command will monopolize Enso's resources and you won't be able to use -Enso until your command has finished executing! - -Class-based Commands --------------------- - -More complex commands can be encapsulated into classes and -instantiated as objects; in fact, all Enso really looks for when -importing commands are callables that start with ``cmd_``. This means -that the following works: -```python - class VoteCommand(object): - def __init__(self, candidates): - self.valid_args = candidates - - def __call__(self, ensoapi, candidate): - ensoapi.display_message("You voted for: %s" % candidate) - - cmd_vote_for = VoteCommand(["barack obama", "john mccain"]) -``` -Command Updating ----------------- - -Some commands may need to do processing while not being executed; for -instance, an ``open`` command that allows the user to open an -application installed on their computer may want to update its -``valid_args`` property whenever a new application is installed or -uninstalled. - -If a command object has an ``on_quasimode_start()`` function attached -to it, it will be called whenever the command quasimode is entered. -This allows the command to do any processing it may need to do. As -with the command execution call itself, ``on_quasimode_start()`` may -use ``yield`` to relegate control back to Enso when it knows that some -operation will take a while to finish. - -Including Other Files ---------------------- - -Python's standard ``import`` statement can be used from command -scripts, of course, but the disadvantage of doing this with evolving -code is that - at present, at least - imported modules won't be reloaded -if their contents change. diff --git a/enso/commands/idgen.py b/enso/commands/idgen.py index 3465a02b..f9f52c94 100644 --- a/enso/commands/idgen.py +++ b/enso/commands/idgen.py @@ -20,7 +20,7 @@ def cmd_guid(ensoapi, format = "lowercase"): random.seed() -boundsParser = re.compile(r"(?:from ?(\d+))? ?(?:to ?(\d+))?") +boundsParser = re.compile(r"(?:\s*from\s+?(\d+))?\s+?(?:to\s+?(\d+))?") def cmd_random(ensoapi, from_num_to_num = ""): """Replaces selected text with a random number""" diff --git a/enso/commands/open.py b/enso/commands/open.py index fc3dd636..55666a26 100644 --- a/enso/commands/open.py +++ b/enso/commands/open.py @@ -157,9 +157,10 @@ def cmd_learn_as_open(ensoapi, name): if is_url(file): shortcut = PyInternetShortcut() + file_path = file_path + ".url" shortcut.SetURL(file) shortcut.QueryInterface( pythoncom.IID_IPersistFile ).Save( - file_path + ".url", 0 ) + file_path, 0 ) else: shortcut = PyShellLink() @@ -167,12 +168,13 @@ def cmd_learn_as_open(ensoapi, name): shortcut.SetWorkingDirectory(os.path.dirname(file)) shortcut.SetIconLocation(file, 0) + file_path = file_path + ".lnk" shortcut.QueryInterface( pythoncom.IID_IPersistFile ).Save( - file_path + ".lnk", 0 ) + file_path, 0 ) #time.sleep(0.5) global shortcuts_map - shortcuts_map = Shortcuts.get().refreshShortcuts() + Shortcuts.get().add_shortcut(file_path) cmd_open.valid_args = [s[1] for s in list(shortcuts_map.values())] cmd_open_with.valid_args = [s[1] for s in list(shortcuts_map.values()) if s[0] == SHORTCUT_TYPE_EXECUTABLE] cmd_unlearn_open.valid_args = [s[1] for s in list(shortcuts_map.values())] @@ -186,17 +188,22 @@ def cmd_unlearn_open(ensoapi, name): file_path = os.path.join(LEARN_AS_DIR, name) if os.path.isfile(file_path + ".lnk"): sl = PyShellLink() - sl.load(file_path + ".lnk") + + file_path = file_path + ".lnk" + sl.load(file_path) unlearn_open_undo.append([name, sl]) - os.remove(file_path + ".lnk") + os.remove(file_path) elif os.path.isfile(file_path + ".url"): sl = PyInternetShortcut() - sl.load(file_path + ".url") + + file_path = file_path + ".url" + sl.load(file_path) unlearn_open_undo.append([name, sl]) - os.remove(file_path + ".url") + os.remove(file_path) global shortcuts_map - shortcuts_map = Shortcuts.get().refreshShortcuts() + + Shortcuts.get().remove_shortcut(name.lower()) cmd_open.valid_args = [s[1] for s in list(shortcuts_map.values())] cmd_open_with.valid_args = [s[1] for s in list(shortcuts_map.values()) if s[0] == SHORTCUT_TYPE_EXECUTABLE] cmd_unlearn_open.valid_args = [s[1] for s in list(shortcuts_map.values())] diff --git a/enso/commands/web_search.py b/enso/commands/web_search.py index e466f593..45d7cf7c 100644 --- a/enso/commands/web_search.py +++ b/enso/commands/web_search.py @@ -7,6 +7,7 @@ import re import xml.sax.saxutils import logging +import json from urllib.parse import urlparse, urlunparse @@ -22,7 +23,7 @@ def __init__(self, target, args = None): threading.Thread.__init__(self) self.__success = False self.__retval = None - self.setDaemon(True) + self.daemon = True self.start() def run(self): @@ -33,12 +34,12 @@ def run(self): self.__success = True def wasSuccessful(self): - if self.isAlive(): + if self.is_alive(): raise Exception( "Thread not finished" ) return self.__success def getRetval(self): - if self.isAlive(): + if self.is_alive(): raise Exception( "Thread not finished" ) return self.__retval @@ -233,7 +234,7 @@ def cmd_is_down(ensoapi, url = None): get_selection_thread = ThreadedFunc( target = ensoapi.get_selection ) - while get_selection_thread.isAlive(): + while get_selection_thread.is_alive(): yield if get_selection_thread.wasSuccessful(): seldict = get_selection_thread.getRetval() @@ -253,28 +254,38 @@ def cmd_is_down(ensoapi, url = None): netloc = parsed_url.netloc if netloc.endswith(":80"): netloc = netloc[:-3] - base_url = scheme + "://" + netloc + #base_url = scheme + "://" + netloc + base_url = netloc print(base_url) - query_url = "http://downforeveryoneorjustme.com/%s" % urllib.parse.quote_plus(base_url) + query_url = "https://api-prod.downfor.cloud/httpcheck/%s" % urllib.parse.quote_plus(base_url) t = ThreadedFunc( target = get_html, args = (ensoapi, query_url)) - while t.isAlive(): + while t.is_alive(): yield if t.wasSuccessful(): - html = t.getRetval() - if html.find("It's just you") > -1: - displayMessage("

Site %s is online

" % base_url) - elif html.find("doesn't look like a site") > -1: - displayMessage("

Site %s is unknown!

" % base_url) - elif html.find("It's not just you") > -1: + result = json.loads(t.getRetval()) + print(result) + if result["isDown"]: displayMessage("

Site %s is down!

" % base_url) - else: - print(html) + else: + displayMessage("

Site %s is online

" % base_url) + else: + displayMessage("

Site %s is unknown!

" % base_url) + + # html = t.getRetval() + # if html.find("It's just you") > -1: + # displayMessage("

Site %s is online

" % base_url) + # elif html.find("doesn't look like a site") > -1: + # displayMessage("

Site %s is unknown!

" % base_url) + # elif html.find("It's not just you") > -1: + # displayMessage("

Site %s is down!

" % base_url) + # else: + # print(html) def cmd_url(ensoapi, parameter = None): @@ -322,7 +333,7 @@ def get_url(): import re thread = ThreadedFunc(make_get_url_func("http://checkip.dyndns.com/")) - while thread.isAlive(): + while thread.is_alive(): yield matches = [] ip = re.search("Address: ([^<]+)", thread.getRetval()).group(1) diff --git a/enso/enso/platform/win32/selection/_ContextUtils.py b/enso/enso/platform/win32/selection/_ContextUtils.py index ecd85cf3..36f9201e 100644 --- a/enso/enso/platform/win32/selection/_ContextUtils.py +++ b/enso/enso/platform/win32/selection/_ContextUtils.py @@ -384,6 +384,7 @@ def translateKey( keyCode ): try: hkl = win32api.GetKeyboardLayout(dwFGThread) sc = win32api.MapVirtualKey(keyCode, 0, hkl) + win32api.GetKeyState(win32con.VK_SHIFT) # shift state is delayed otherwise. magic! kbst = win32api.GetKeyboardState() kbst_buff = to_ctypes_char_buffer(kbst) diff --git a/enso/enso/platform/win32/shortcuts/Shortcuts.py b/enso/enso/platform/win32/shortcuts/Shortcuts.py index 415c52cb..e4ac758c 100644 --- a/enso/enso/platform/win32/shortcuts/Shortcuts.py +++ b/enso/enso/platform/win32/shortcuts/Shortcuts.py @@ -535,23 +535,34 @@ def _get_shortcuts(self, directory): for filename in filenames: if ignored.search(filename): continue - name, ext = os.path.splitext(filename) - if not ext.lower() in (".lnk", ".url"): - continue - - shortcut_type = SHORTCUT_TYPE_DOCUMENT - shortcut_path = "" + self._add_shortcut(dirpath, filename, shortcuts, sl) + return shortcuts - if ext.lower() == ".lnk": - shortcut_path = os.path.join(dirpath, filename) - sl.load(shortcut_path) - shortcut_type = sl.get_type() - elif ext.lower() == ".url": - shortcut_path = os.path.join(dirpath, filename) - shortcut_type = SHORTCUT_TYPE_URL + def _add_shortcut(self, dirpath, filename, shortcuts, sl): + name, ext = os.path.splitext(filename) + if not ext.lower() in (".lnk", ".url"): + return False + shortcut_type = SHORTCUT_TYPE_DOCUMENT + shortcut_path = "" + if ext.lower() == ".lnk": + shortcut_path = os.path.join(dirpath, filename) + sl.load(shortcut_path) + shortcut_type = sl.get_type() + elif ext.lower() == ".url": + shortcut_path = os.path.join(dirpath, filename) + shortcut_type = SHORTCUT_TYPE_URL + shortcuts.append((shortcut_type, name.lower(), shortcut_path)) + return True + + def add_shortcut(self, filename): + shortcuts = [] + sl = PyShellLink() + dirpath, filename = os.path.split(filename) + self._add_shortcut(dirpath, filename, shortcuts, sl) + self._shortcuts_map[shortcuts[0][1]] = shortcuts[0] - shortcuts.append((shortcut_type, name.lower(), shortcut_path)) - return shortcuts + def remove_shortcut(self, name): + del self._shortcuts_map[name] def _get_universal_windows_apps(self): shortcuts = [] diff --git a/enso/enso/strings.py b/enso/enso/strings.py index 9386dbb7..0a075414 100644 --- a/enso/enso/strings.py +++ b/enso/enso/strings.py @@ -1,5 +1,5 @@ # Enso version for use in UI -ENSO_VERSION = "0.9.0" +ENSO_VERSION = "0.9.1" # The message displayed when the user types some text that is not a command. BAD_COMMAND_MSG = "

%s is not a command.

" \ diff --git a/enso/enso/webui/API.html b/enso/enso/webui/API.html index bb7fb273..b21e493e 100644 --- a/enso/enso/webui/API.html +++ b/enso/enso/webui/API.html @@ -44,7 +44,7 @@

ensoapi object methods

display_message(msg, caption=None)

Displays the given message, with an optional caption. Both - parameters should be unicode strings.

+ parameters should be Unicode strings.

get_selection()

@@ -59,7 +59,7 @@

set_selection(seldict)

Alternatively, if a string is provided instead of a - dictionary, the current selection is set to the unicode + dictionary, the current selection is set to the Unicode contents of the string.

@@ -75,21 +75,21 @@

dictionary_probe(category, The category parameter specifies the name of the generated command argument.
If findfirst is true and the dictionary item value is a directory path, the first file found in the directory is sent into the player instead of the item.
- If player is empty string, the default shell application is used.
+ If the player argument is an empty string, the default shell application is used.
The 'all' command argument value is substituted by the all function parameter.

directory_probe(category, directory, player="", additional=None)

-

Sends directory entries found in the directory to player. Obtains command arguments from the directory entries.

+

Sends directory entries found in the directory to the player. Obtains command arguments from the directory entries.

The category parameter specifies the name of the generated command argument.
- The additional function parameter allows to create additional command argument values (from a supplied dictionary), not found in the directory.

+ The additional function parameter allows the creation of additional command argument values (from a supplied dictionary), not found in the directory.

findfirst_probe(category, dictionary, player="")

-

Uses the default shell program to open a first file in the directory; uses the player if given. Obtains command arguments from the directory entries.

- The category parameter stets the name of the generated command argument.

+

Uses the default shell program to open the first file in the directory; uses the player if given. Obtains command arguments from the directory entries.

+ The category parameter sets the name of the generated command argument.

\ No newline at end of file diff --git a/enso/enso/webui/tutorial.html b/enso/enso/webui/tutorial.html index adce1a59..fa883c9d 100644 --- a/enso/enso/webui/tutorial.html +++ b/enso/enso/webui/tutorial.html @@ -57,7 +57,7 @@

Configuring Enso

-

'Custom Initialization' block at the Enso settings page allows to specify any +

'Custom Initialization' block at the Enso settings page allows specifying any Python code needed to initialize Enso. By using it, you may override variables from config.py or provide values required by some commands. You can access variables declared at this block in your own commands through the 'config' module. For example, you can obtain MY_VARIABLE defined at the configuration block: @@ -93,7 +93,7 @@

Interacting with The Current Selection

-

To obtain the current selection, use ensoapi.get_selection(). - This method returns a selection dictionary, or seldict for short. A - seldict is simply a dictionary that maps a data format identifier to - selection data in that format.

+ ensoapi.display_message("Hello World!") + +

Interacting with + The Current Selection

+

Interacting with the selection is the primary method to get values in and out of Enso + commands. To obtain the current selection, use ensoapi.get_selection(). This + method returns a selection dictionary, or seldict for short. A seldict is simply a + dictionary that maps a data format identifier to selection data in that format.

Some valid data formats in a seldict are: