Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package vit for openSUSE:Factory checked in at 2023-04-24 22:31:19 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/vit (Old) and /work/SRC/openSUSE:Factory/.vit.new.1533 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "vit" Mon Apr 24 22:31:19 2023 rev:8 rq:1082340 version:2.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/vit/vit.changes 2022-05-25 20:34:46.436245368 +0200 +++ /work/SRC/openSUSE:Factory/.vit.new.1533/vit.changes 2023-04-24 22:31:30.739617306 +0200 @@ -1,0 +2,20 @@ +Mon Apr 24 03:35:41 UTC 2023 - Mia Herkt <m...@0x0.st> + +- Update to 2.3.0 + * Allow flash configuration + * correctly calculate text width of full-width characters + * Fix required minimum Python version + * add documentation for auto-refresh configuration + * place IS_VIT_INSTANCE into environ earlier + * example hook for intelligent VIT refresh + * sample script to externally refresh VIT instances + * add Bash function example for vit wrapper + * add pid_dir option to [vit] section + See sample config.ini for details on usage + * add basic signal support + + SIGUSR1: refresh (equivalent to hitting refresh key in VIT) + + SIGTERM/SIGINT/SIGQUIT: quit VIT cleanly + * more user-friendly error message for unsupported color + definitions + +------------------------------------------------------------------- Old: ---- vit-2.2.0.tar.gz New: ---- vit-2.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ vit.spec ++++++ --- /var/tmp/diff_new_pack.fVgHij/_old 2023-04-24 22:31:32.255626317 +0200 +++ /var/tmp/diff_new_pack.fVgHij/_new 2023-04-24 22:31:32.263626365 +0200 @@ -1,7 +1,7 @@ # # spec file for package vit # -# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2023 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: vit -Version: 2.2.0 +Version: 2.3.0 Release: 0 Summary: Visual Interactive Taskwarrior full-screen terminal interface License: MIT ++++++ vit-2.2.0.tar.gz -> vit-2.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/CUSTOMIZE.md new/vit-2.3.0/CUSTOMIZE.md --- old/vit-2.2.0/CUSTOMIZE.md 2021-11-04 14:51:45.000000000 +0100 +++ new/vit-2.3.0/CUSTOMIZE.md 2023-04-14 06:10:49.000000000 +0200 @@ -94,7 +94,7 @@ ```python # keybinding/keybinding.py -class Keybinding(object): +class Keybinding: def replacements(self): def _custom_match(variable): if variable == 'TEST': @@ -140,3 +140,33 @@ Now, for example, to jump to a task whose ID is 42, you need to press `4`, `2` and `<Enter>`, instead of `:`, `4`, `2` and `<Enter>`. This saves a `:` keypress whenever jumping to a task. + +### Auto-refreshing VIT's interface + +*Note: Windows unfortunately does not support the `SIGUSR1` signal, so this feature is not currently available in that environment.* + +VIT was designed to be used in a request/response manner with the underlying TaskWarrior database, and by default its interface does not refresh when there are other changes happening outside of a specific VIT instance. + +However, VIT provides some basic mechanisms that, when combined, allow for an easy implementation of an auto-refreshing interface: + +* **Signal handling:** Sending the `SIGUSR1` signal to a VIT process will cause it to refresh its interface (the equivalent of the `{ACTION_REFRESH}` action keybinding +* **PID management:** Configuring `pid_dir` in `config.ini` will cause VIT to manage PID files in `pid_dir`. Executing VIT with the `--list-pids` argument will output all current PIDs in `pid_dir` to standard output +* **Instance environment variable:** VIT injects the `IS_VIT_INSTANCE` environment variable into the environment of the running process. As such, processes invoked in that environment have access to the variable + +#### Refresh all VIT instances example + +[vit-external-refresh.sh](scripts/vit-external-refresh.sh) provides an example leveraging signals to externally refresh all local VIT interfaces. To use, make sure: + +* The script is executable, and in your `PATH` environment variable +* You've properly set `pid_dir` in `config.ini` + +#### Refresh VIT when TaskWarrior updates a task + +[on-exit-refresh-vit.sh](scripts/hooks/on-exit-refresh-vit.sh) provides an example TaskWarrior hook that will automatically refresh all local VIT interfaces when using Taskwarrior directly. To use, make sure: + +* The script is executable, [named properly](https://taskwarrior.org/docs/hooks.html), and placed in TaskWarrior's hooks directory, usually `.task/hooks` +* [vit-external-refresh.sh](scripts/vit-external-refresh.sh) is configured correctly as above + +#### Other refresh scenarios + +The basic tools above can be used in other more complex scenarios, such as refreshing VIT's interface after an automated script updates one or more tasks. The implementation details will vary with the use case, and are left as an exercise for the user. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/DEVELOPMENT.md new/vit-2.3.0/DEVELOPMENT.md --- old/vit-2.2.0/DEVELOPMENT.md 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/DEVELOPMENT.md 2022-04-28 04:43:50.000000000 +0200 @@ -6,8 +6,17 @@ 4. ```vit/command_line.py``` is the entry point for the application. To run it without a full installation: * Set the ```PYTHONPATH``` environment variable to the root directory of the repository * Run it with ```python vit/command_line.py``` - * A snazzier option is to create a command line alias. For bash: - * ```alias vit='PYTHONPATH=[path_to_root_dir] python vit/command_line.py'``` + * A snazzier option is to create a command line alias. For Bash: + ```bash + alias vit='PYTHONPATH=[path_to_root_dir] python vit/command_line.py' + ``` + * ...or a shell function. For Bash: + ```bash + vit() { + cd ~/git/vit && PYTHONPATH=${HOME}/git/vit python vit/command_line.py "$@" + } + export -f vit + ``` ### Tests * Located in the ```tests``` directory @@ -37,6 +46,28 @@ * Reports are generated via custom code in VIT, which allows extra features not found in Taskwarrior. Most report-related settings are read directly from the Taskwarrior configuration, which *mostly* allows a single point of configuration * Data is written to Taskwarrior using a combination of ```import``` commands driven by [tasklib](https://github.com/robgolding/tasklib), and CLI calls for more complex scenarios +### Release checklist + +For any developer managing VIT releases, here's a simple checklist: + +#### Pre-release + + * Check `requirements.txt`, and bump any dependencies if necessary + * Check `setup.py`, and bump the minimum Python version if the current version is no longer supported + +#### Release + + * Bump the release number in `vit/version.py` + * Generate changelog entries using `scripts/generate-changelog-entries.sh` + * Add changelog entries to `CHANGES.md` + * Commit + * Add the proper git tag and push it + * Create a Github release for the tag, use the generated changelog entries in the description + * Build and publish PyPi releases using `scripts/release-pypi.sh` + +#### Post-release + + * Announce on all relevant channels ### Roadmap diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/PKG-INFO new/vit-2.3.0/PKG-INFO --- old/vit-2.2.0/PKG-INFO 2022-04-18 01:07:49.000000000 +0200 +++ new/vit-2.3.0/PKG-INFO 2023-04-14 06:27:09.153355400 +0200 @@ -1,13 +1,11 @@ Metadata-Version: 2.1 Name: vit -Version: 2.2.0 +Version: 2.3.0 Summary: Visual Interactive Taskwarrior full-screen terminal interface Home-page: https://github.com/vit-project/vit Author: Chad Phillips Author-email: c...@apartmentlines.com -License: UNKNOWN Keywords: taskwarrior,console,tui,text-user-interface -Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: MIT License Classifier: Environment :: Console @@ -43,7 +41,7 @@ ## Requirements * [Taskwarrior](https://taskwarrior.org) - * [Python](https://www.python.org) 3.5+ + * [Python](https://www.python.org) 3.7+ * [pip](https://pypi.org/project/pip) ## Installation @@ -85,5 +83,3 @@ Taskwarrior Team supp...@taskwarrior.org - - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/README.md new/vit-2.3.0/README.md --- old/vit-2.2.0/README.md 2021-11-04 14:51:57.000000000 +0100 +++ new/vit-2.3.0/README.md 2022-05-09 16:29:06.000000000 +0200 @@ -22,7 +22,7 @@ ## Requirements * [Taskwarrior](https://taskwarrior.org) - * [Python](https://www.python.org) 3.5+ + * [Python](https://www.python.org) 3.7+ * [pip](https://pypi.org/project/pip) ## Installation diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/setup.py new/vit-2.3.0/setup.py --- old/vit-2.2.0/setup.py 2022-04-18 00:54:13.000000000 +0200 +++ new/vit-2.3.0/setup.py 2023-04-14 06:10:49.000000000 +0200 @@ -5,7 +5,7 @@ DEFAULT_BRANCH = "2.x" BASE_GITHUB_URL = "https://github.com/vit-project/vit/blob" -MARKUP_LINK_REGEX = "\[([^]]+)\]\(([\w]+\.md)\)" +MARKUP_LINK_REGEX = r"\[([^]]+)\]\(([\w]+\.md)\)" FILE_DIR = path.dirname(path.abspath(path.realpath(__file__))) with open(path.join(FILE_DIR, 'README.md')) as f: @@ -21,7 +21,7 @@ setup( name='vit', - packages=['vit', 'vit.formatter', 'vit.theme'], + packages=['vit', 'vit.config', 'vit.formatter', 'vit.keybinding', 'vit.theme'], description="Visual Interactive Taskwarrior full-screen terminal interface", long_description=README, long_description_content_type='text/markdown', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/action_manager.py new/vit-2.3.0/vit/action_manager.py --- old/vit-2.2.0/vit/action_manager.py 2021-11-22 15:10:12.000000000 +0100 +++ new/vit-2.3.0/vit/action_manager.py 2023-04-14 06:10:49.000000000 +0200 @@ -1,6 +1,6 @@ import uuid -class ActionManagerRegistrar(object): +class ActionManagerRegistrar: def __init__(self, registry): self.registry = registry self.uuid = uuid.uuid4() @@ -37,7 +37,7 @@ return actions[keybindings[keys]['action_name']] return None -class ActionManagerRegistry(object): +class ActionManagerRegistry: def __init__(self, action_registry, keybindings, event=None): self.actions = {} self.action_registry = action_registry diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/actions.py new/vit-2.3.0/vit/actions.py --- old/vit-2.2.0/vit/actions.py 2021-11-04 14:51:57.000000000 +0100 +++ new/vit-2.3.0/vit/actions.py 2023-04-14 06:10:49.000000000 +0200 @@ -1,4 +1,4 @@ -class Actions(object): +class Actions: def __init__(self, action_registry): self.action_registry = action_registry diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/application.py new/vit-2.3.0/vit/application.py --- old/vit-2.2.0/vit/application.py 2021-11-22 15:12:28.000000000 +0100 +++ new/vit-2.3.0/vit/application.py 2023-04-14 06:10:49.000000000 +0200 @@ -2,6 +2,8 @@ from importlib import import_module +import os +import signal import subprocess # TODO: Use regex module for better PCRE support? # https://bitbucket.org/mrabarnett/mrab-regex @@ -35,6 +37,7 @@ from vit.registry import ActionRegistry, RequestReply from vit.action_manager import ActionManagerRegistry from vit.denotation import DenotationPopupLauncher +from vit.pid_manager import PidManager # NOTE: This entire class is a workaround for the fact that urwid catches the # 'ctrl l' keypress in its unhandled_input code, and prevents that from being @@ -67,16 +70,39 @@ else: return super().keypress(size, key) -class Application(): +class Application: def __init__(self, option, filters): self.extra_filters = filters self.loader = Loader() self.load_early_config() self.set_report() self.setup_main_loop() + self.setup_signal_listeners() self.refresh(False) + self.setup_pid() self.loop.run() + def setup_signal_listeners(self): + # Since not all platforms may have all signals, ensure they are + # supported before adding a handler. + if hasattr(signal, 'SIGUSR1'): + pipe = self.loop.watch_pipe(self.async_refresh) + def sigusr1_handler(signum, frame): + os.write(pipe, b'x') + signal.signal(signal.SIGUSR1, sigusr1_handler) + if hasattr(signal, 'SIGTERM'): + def sigterm_handler(signum, frame): + self.signal_quit("SIGTERM") + signal.signal(signal.SIGTERM, sigterm_handler) + if hasattr(signal, 'SIGINT'): + def sigint_handler(signum, frame): + self.signal_quit("SIGINT") + signal.signal(signal.SIGINT, sigint_handler) + if hasattr(signal, 'SIGQUIT'): + def sigquit_handler(signum, frame): + self.signal_quit("SIGQUIT") + signal.signal(signal.SIGQUIT, sigquit_handler) + def load_early_config(self): self.config = ConfigParser(self.loader) self.task_config = TaskParser(self.config) @@ -97,6 +123,13 @@ except: pass + def setup_pid(self): + self.pid_manager = PidManager(self.config) + self.pid_manager.setup() + + def teardown_pid(self): + self.pid_manager.teardown() + def set_active_context(self): self.context = self.task_config.get_active_context() @@ -183,7 +216,7 @@ def default_keybinding_replacements(self): import json from datetime import datetime - task_replacement_match = re.compile("^TASK_(\w+)$") + task_replacement_match = re.compile(r"^TASK_(\w+)$") def _task_attribute_match(variable): matches = re.match(task_replacement_match, variable) if matches: @@ -509,20 +542,21 @@ rows = self.table.rows current_index = start_index last_index = len(rows) - 1 - start_matches = self.search_row_has_search_term(rows[start_index], search_regex) - current_index = self.search_increment_index(current_index, reverse) - while True: - if reverse and current_index < 0: - self.search_loop_warning('TOP', reverse) - current_index = last_index - elif not reverse and current_index > last_index: - self.search_loop_warning('BOTTOM', reverse) - current_index = 0 - if self.search_row_has_search_term(rows[current_index], search_regex): - return current_index - if current_index == start_index: - return start_index if start_matches else None + if len(rows) > 0: + start_matches = self.search_row_has_search_term(rows[start_index], search_regex) current_index = self.search_increment_index(current_index, reverse) + while True: + if reverse and current_index < 0: + self.search_loop_warning('TOP', reverse) + current_index = last_index + elif not reverse and current_index > last_index: + self.search_loop_warning('BOTTOM', reverse) + current_index = 0 + if self.search_row_has_search_term(rows[current_index], search_regex): + return current_index + if current_index == start_index: + return start_index if start_matches else None + current_index = self.search_increment_index(current_index, reverse) def search_increment_index(self, current_index, reverse=False): return current_index + (-1 if reverse else 1) @@ -564,7 +598,13 @@ pass return False, False + def signal_quit(self, signal): + #import debug + #debug.file("VIT received %s signal, quitting" % signal) + self.quit() + def quit(self): + self.teardown_pid() raise urwid.ExitMainLoop() def build_task_table(self): @@ -916,6 +956,9 @@ else: raise RuntimeError("Error retrieving completed tasks: %s" % stderr) + def async_refresh(self, _): + self.refresh() + def refresh(self, load_early_config=True): self.bootstrap(load_early_config) self.build_main_widget() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/autocomplete.py new/vit-2.3.0/vit/autocomplete.py --- old/vit-2.2.0/vit/autocomplete.py 2021-11-04 14:51:45.000000000 +0100 +++ new/vit-2.3.0/vit/autocomplete.py 2023-04-14 06:10:49.000000000 +0200 @@ -5,7 +5,7 @@ from vit import util from vit.process import Command -class AutoComplete(object): +class AutoComplete: def __init__(self, config, default_filters=None, extra_filters=None): self.default_filters = default_filters or ('column', 'project', 'tag') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/base_list_box.py new/vit-2.3.0/vit/base_list_box.py --- old/vit-2.2.0/vit/base_list_box.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/base_list_box.py 2022-10-26 15:33:43.000000000 +0200 @@ -71,11 +71,13 @@ self.keypress(size, '<Page Down>') def keypress_home(self, size): - self.set_focus(0) + if len(self.body) > 0: + self.set_focus(0) def keypress_end(self, size): - self.set_focus(len(self.body) - 1) - self.set_focus_valign('bottom') + if len(self.body) > 0: + self.set_focus(len(self.body) - 1) + self.set_focus_valign('bottom') def keypress_screen_top(self, size): top, _, _ = self.get_top_middle_bottom_rows(size) @@ -93,8 +95,9 @@ self.set_focus(bottom.position) def keypress_focus_valign_center(self, size): - self.set_focus(self.focus_position) - self.set_focus_valign('middle') + if len(self.body) > 0: + self.set_focus(self.focus_position) + self.set_focus_valign('middle') def transform_special_keys(self, key): # NOTE: These are special key presses passed to allow navigation diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/color.py new/vit-2.3.0/vit/color.py --- old/vit-2.2.0/vit/color.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/color.py 2023-04-14 06:10:49.000000000 +0200 @@ -9,7 +9,11 @@ 'underline', ] -class TaskColorConfig(object): +INVALID_COLOR_MODIFIERS = [ + 'inverse', +] + +class TaskColorConfig: """Colorized task output. """ def __init__(self, config, task_config, theme, theme_alt_backgrounds): @@ -24,7 +28,7 @@ # without pipes, the 'color' config setting in Taskwarrior is not used, and # instead a custom setting is used. self.color_enabled = self.config.get('color', 'enabled') - self.display_attrs_available, self.display_attrs = self.convert_color_config(self.task_config.filter_to_dict('^color\.')) + self.display_attrs_available, self.display_attrs = self.convert_color_config(self.task_config.filter_to_dict(r'^color\.')) self.project_display_attrs = self.get_project_display_attrs() if self.include_subprojects: self.add_project_children() @@ -96,6 +100,11 @@ remapped_colors = self.map_named_colors(sorted_parts) return ','.join(remapped_colors) + def check_invalid_color_parts(self, color_parts): + invalid_color_parts = {*color_parts} & {*INVALID_COLOR_MODIFIERS} + if invalid_color_parts: + raise ValueError("The following TaskWarrior color definitions are unsupported in VIT: %s -- read the documentation for possible workarounds" % ", ".join(invalid_color_parts)) + def map_named_colors(self, color_parts): if len(color_parts) > 0 and color_parts[0] in self.task_256_to_urwid_256: color_parts[0] = self.task_256_to_urwid_256[color_parts[0]] @@ -103,7 +112,9 @@ def make_color_parts(self, foreground, background): foreground_parts = self.split_color_parts(foreground) + self.check_invalid_color_parts(foreground_parts) background_parts = self.split_color_parts(background) + self.check_invalid_color_parts(background_parts) return foreground_parts, background_parts def split_color_parts(self, color_parts): @@ -123,8 +134,8 @@ return 0 return sorted(color_parts, key=cmp_to_key(comparator)) -class TaskColorizer(object): - class Decorator(object): +class TaskColorizer: + class Decorator: def color_enabled(func): @wraps(func) def verify_color_enabled(self, *args, **kwargs): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/command_bar.py new/vit-2.3.0/vit/command_bar.py --- old/vit-2.2.0/vit/command_bar.py 2020-12-28 18:26:46.000000000 +0100 +++ new/vit-2.3.0/vit/command_bar.py 2023-04-14 06:10:49.000000000 +0200 @@ -95,7 +95,7 @@ def set_edit_text_callback(self): return self.set_edit_text -class CommandBarHistory(object): +class CommandBarHistory: """Holds command-specific history for the command bar. """ def __init__(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/config/config.sample.ini new/vit-2.3.0/vit/config/config.sample.ini --- old/vit-2.2.0/vit/config/config.sample.ini 2021-11-04 14:51:57.000000000 +0100 +++ new/vit-2.3.0/vit/config/config.sample.ini 2022-10-21 18:59:04.000000000 +0200 @@ -57,6 +57,24 @@ #included in the active filter for this setting to have effect. #focus_on_add = False +# Path to a directory to manage pid files for running instances of VIT. +# If no path is provided, no pid files will be managed. +# The special token $UID may be used, and will be substituted with the user ID +# of the user starting VIT. +# VIT can be run with the '--list-pids' argument, which will output a list of +# all pids in pid_dir; useful for sending signals to the running processes. +# If you use this feature, it's suggested to choose a directory that is +# automatically cleaned on boot, e.g.: +# /var/run/user/$UID/vit +# /tmp/vit_pids +#pid_dir = + +# Int. The number of flash repetitions focusing on the edit made +#flash_focus_repeat_times = 2 + +# Float. Waiting time for the blink focusing on the edit made +#flash_focus_pause_seconds = 0.1 + [report] # The default Taskwarrior report to load when VIT first starts, if no report diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/config_parser.py new/vit-2.3.0/vit/config_parser.py --- old/vit-2.2.0/vit/config_parser.py 2022-03-06 02:27:52.000000000 +0100 +++ new/vit-2.3.0/vit/config_parser.py 2023-04-14 06:10:49.000000000 +0200 @@ -18,9 +18,9 @@ SORT_ORDER_CHARACTERS = ['+', '-'] SORT_COLLATE_CHARACTERS = ['/'] VIT_CONFIG_FILE = 'config.ini' -FILTER_EXCLUSION_REGEX = re.compile('^limit:') -FILTER_PARENS_REGEX = re.compile('([\(\)])') -CONFIG_BOOLEAN_TRUE_REGEX = re.compile('1|yes|true', re.IGNORECASE) +FILTER_EXCLUSION_REGEX = re.compile(r'^limit:') +FILTER_PARENS_REGEX = re.compile(r'([\(\)])') +CONFIG_BOOLEAN_TRUE_REGEX = re.compile(r'1|yes|true', re.IGNORECASE) # TaskParser expects clean hierarchies in the Taskwarrior dotted config names. # However, this is occasionally violated, with a leaf ending in both a string # value and another branch. The below list contains the config values that @@ -46,6 +46,9 @@ 'mouse': False, 'abort_backspace': False, 'focus_on_add': False, + 'pid_dir': '', + 'flash_focus_repeat_times': 2, + 'flash_focus_pause_seconds': 0.1, }, 'report': { 'default_report': 'next', @@ -94,7 +97,7 @@ 'J': '%j', # 3 digit day of year number, sometimes referred to as a Julian date, eg '001', '011', or '365' } -class ConfigParser(object): +class ConfigParser: def __init__(self, loader): self.loader = loader self.config = configparser.SafeConfigParser() @@ -165,7 +168,7 @@ try: value = self.config.get(section, key) return self.transform(key, value, default) - except (configparser.NoSectionError, configparser.NoOptionError): + except (configparser.NoSectionError, configparser.NoOptionError, ValueError): return default def items(self, section): @@ -180,12 +183,22 @@ def transform(self, key, value, default): if isinstance(default, bool): return self.transform_bool(value) + elif isinstance(default, int): + return self.transform_int(value) + elif isinstance(default, float): + return self.transform_float(value) else: return value def transform_bool(self, value): return True if CONFIG_BOOLEAN_TRUE_REGEX.match(value) else False + def transform_int(self, value): + return int(value) + + def transform_float(self, value): + return float(value) + def get_taskrc_path(self): taskrc_path = os.path.expanduser('TASKRC' in env.user and env.user['TASKRC'] or self.get('taskwarrior', 'taskrc')) @@ -211,7 +224,13 @@ def is_mouse_enabled(self): return self.get('vit', 'mouse') -class TaskParser(object): + def get_flash_focus_repeat_times(self): + return self.get('vit', 'flash_focus_repeat_times') + + def get_flash_focus_pause_seconds(self): + return self.get('vit', 'flash_focus_pause_seconds') + +class TaskParser: def __init__(self, config): self.config = config self.task_config = [] @@ -275,7 +294,7 @@ def subtree(self, matcher, walk_subtree=True): matcher_regex = matcher if walk_subtree: - matcher_regex = r'%s' % (('^%s' % matcher).replace('.', '\.')) + matcher_regex = r'%s' % (('^%s' % matcher).replace(r'.', r'\.')) full_tree = {} lines = self.filter(matcher_regex) for (hierarchy, value) in lines: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/env.py new/vit-2.3.0/vit/env.py --- old/vit-2.2.0/vit/env.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/env.py 2022-04-28 21:54:53.000000000 +0200 @@ -1,3 +1,5 @@ import os +os.environ['IS_VIT_INSTANCE'] = "1" + user = os.environ.copy() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/event.py new/vit-2.3.0/vit/event.py --- old/vit-2.2.0/vit/event.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/event.py 2023-04-14 06:10:49.000000000 +0200 @@ -1,5 +1,5 @@ # TODO: Use urwid signals instead of custom event emitter? -class Emitter(object): +class Emitter: """Simple event listener/emitter. """ def __init__(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/__init__.py new/vit-2.3.0/vit/formatter/__init__.py --- old/vit-2.2.0/vit/formatter/__init__.py 2022-03-23 00:27:36.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/__init__.py 2023-04-14 06:10:49.000000000 +0200 @@ -5,6 +5,8 @@ except ImportError: from backports.zoneinfo import ZoneInfo +from vit.util import unicode_len + TIME_UNIT_MAP = { 'seconds': { 'label': 's', @@ -42,7 +44,7 @@ }, } -class Formatter(object): +class Formatter: def __init__(self, column, report, formatter_base, blocking_task_uuids, **kwargs): self.column = column self.report = report @@ -54,7 +56,7 @@ if not obj: return self.empty() obj = str(obj) - return (len(obj), self.markup_element(obj)) + return (unicode_len(obj), self.markup_element(obj)) def empty(self): return (0, '') @@ -64,7 +66,7 @@ def markup_none(self, color): if color: - return (len(self.formatter.none_label), (color, self.formatter.none_label)) + return (unicode_len(self.formatter.none_label), (color, self.formatter.none_label)) else: return self.empty() @@ -97,7 +99,7 @@ if not obj: return self.empty() formatted_duration = self.format_duration(obj) - return (len(formatted_duration), self.markup_element(obj, formatted_duration)) + return (unicode_len(formatted_duration), self.markup_element(obj, formatted_duration)) def format_duration(self, obj): return obj @@ -114,7 +116,7 @@ if not dt: return self.empty() formatted_date = self.format_datetime(dt, task) - return (len(formatted_date), self.markup_element(dt, formatted_date, task)) + return (unicode_len(formatted_date), self.markup_element(dt, formatted_date, task)) def format_datetime(self, dt, task): return dt.strftime(self.custom_formatter or self.formatter.report) @@ -210,7 +212,7 @@ if not obj: return self.empty() formatted = self.format_list(obj, task) - return (len(formatted), self.markup_element(obj, formatted)) + return (unicode_len(formatted), self.markup_element(obj, formatted)) def markup_element(self, obj, formatted): return (self.colorize(obj), formatted) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/description.py new/vit-2.3.0/vit/formatter/description.py --- old/vit-2.2.0/vit/formatter/description.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/description.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,12 +1,13 @@ from functools import reduce from vit.formatter import String +from vit.util import unicode_len class Description(String): def format(self, description, task): if not description: return self.empty() - width = len(description) + width = unicode_len(description) colorized_description = self.colorize_description(description) if task['annotations']: annotation_width, colorized_description = self.format_combined(colorized_description, task) @@ -15,7 +16,7 @@ return (width, colorized_description) def format_description_truncated(self, description): - return '%s...' % description[:self.formatter.description_truncate_len] if len(description) > self.formatter.description_truncate_len else description + return '%s...' % description[:self.formatter.description_truncate_len] if unicode_len(description) > self.formatter.description_truncate_len else description def format_combined(self, colorized_description, task): annotation_width, formatted_annotations = self.format_annotations(task) @@ -25,7 +26,7 @@ def reducer(accum, annotation): width, formatted_list = accum formatted = self.format_annotation(annotation) - new_width = len(formatted) + new_width = unicode_len(formatted) if new_width > width: width = new_width formatted_list.append(formatted) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/description_count.py new/vit-2.3.0/vit/formatter/description_count.py --- old/vit-2.2.0/vit/formatter/description_count.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/description_count.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,10 +1,11 @@ from vit.formatter.description import Description +from vit.util import unicode_len class DescriptionCount(Description): def format(self, description, task): if not description: return self.empty() - width = len(description) + width = unicode_len(description) colorized_description = self.colorize_description(description) if not task['annotations']: return (width, colorized_description) @@ -14,7 +15,7 @@ def format_count(self, colorized_description, task): count_string = self.format_annotation_count(task) - return len(count_string), colorized_description + [(None, count_string)] + return unicode_len(count_string), colorized_description + [(None, count_string)] def format_annotation_count(self, task): return " [%d]" % len(task['annotations']) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/description_desc.py new/vit-2.3.0/vit/formatter/description_desc.py --- old/vit-2.2.0/vit/formatter/description_desc.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/description_desc.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,8 +1,9 @@ from vit.formatter.description import Description +from vit.util import unicode_len class DescriptionDesc(Description): def format(self, description, task): if not description: return self.empty() colorized_description = self.colorize_description(description) - return (len(description), colorized_description) + return (unicode_len(description), colorized_description) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/description_truncated.py new/vit-2.3.0/vit/formatter/description_truncated.py --- old/vit-2.2.0/vit/formatter/description_truncated.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/description_truncated.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,10 +1,11 @@ from vit.formatter.description import Description +from vit.util import unicode_len class DescriptionTruncated(Description): def format(self, description, task): if not description: return self.empty() truncated_description = self.format_description_truncated(description) - width = len(truncated_description) + width = unicode_len(truncated_description) colorized_description = self.colorize_description(truncated_description) return (width, colorized_description) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/description_truncated_count.py new/vit-2.3.0/vit/formatter/description_truncated_count.py --- old/vit-2.2.0/vit/formatter/description_truncated_count.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/description_truncated_count.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,11 +1,12 @@ from vit.formatter.description_count import DescriptionCount +from vit.util import unicode_len class DescriptionTruncatedCount(DescriptionCount): def format(self, description, task): if not description: return self.empty() truncated_description = self.format_description_truncated(description) - width = len(truncated_description) + width = unicode_len(truncated_description) colorized_description = self.colorize_description(truncated_description) if not task['annotations']: return (width, colorized_description) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/markers.py new/vit-2.3.0/vit/formatter/markers.py --- old/vit-2.2.0/vit/formatter/markers.py 2021-10-19 15:36:03.000000000 +0200 +++ new/vit-2.3.0/vit/formatter/markers.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,5 +1,6 @@ import unicodedata from vit.formatter import Marker +from vit.util import unicode_len class Markers(Marker): def format(self, _, task): @@ -36,7 +37,7 @@ def add_label(self, color, label, width, text_markup): if self.color_required(color) or not label: return width, text_markup - width += len(label) + len([c for c in label if unicodedata.east_asian_width(c) == 'W']) + width += unicode_len(label) text_markup += [(color, label)] return width, text_markup diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/project.py new/vit-2.3.0/vit/formatter/project.py --- old/vit-2.2.0/vit/formatter/project.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/project.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,4 +1,5 @@ from vit.formatter import String +from vit.util import unicode_len class Project(String): def __init__(self, column, report, defaults, blocking_task_uuids, **kwargs): @@ -9,7 +10,7 @@ return self.format_project(project, task) if project else self.markup_none(self.colorizer.project_none()) def format_project(self, project, task): - return self.format_subproject_indented(project, task) if self.indent_subprojects else (len(project), self.markup_element(project)) + return self.format_subproject_indented(project, task) if self.indent_subprojects else (unicode_len(project), self.markup_element(project)) def format_subproject_indented(self, project, task): parts = project.split('.') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/project_parent.py new/vit-2.3.0/vit/formatter/project_parent.py --- old/vit-2.2.0/vit/formatter/project_parent.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/project_parent.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,8 +1,9 @@ from vit import util from vit.formatter.project import Project +from vit.util import unicode_len class ProjectParent(Project): def format(self, project, task): parent = util.project_get_root(project) - return (len(parent), self.markup_element(parent)) if parent else self.markup_none(self.colorizer.project_none()) + return (unicode_len(parent), self.markup_element(parent)) if parent else self.markup_none(self.colorizer.project_none()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/tags.py new/vit-2.3.0/vit/formatter/tags.py --- old/vit-2.2.0/vit/formatter/tags.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/tags.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,4 +1,5 @@ from vit.formatter import Formatter +from vit.util import unicode_len class Tags(Formatter): def format(self, tags, task): @@ -6,13 +7,13 @@ return self.markup_none(self.colorizer.tag_none()) elif len(tags) == 1: tag = list(tags)[0] - return (len(tag), self.markup_element(tag)) + return (unicode_len(tag), self.markup_element(tag)) else: last_tag = list(tags)[-1] width = 0 text_markup = [] for tag in tags: - width += len(tag) + width += unicode_len(tag) text_markup += [self.markup_element(tag)] if tag != last_tag: width += 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/uda_date.py new/vit-2.3.0/vit/formatter/uda_date.py --- old/vit-2.2.0/vit/formatter/uda_date.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/uda_date.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,5 +1,6 @@ import datetime from vit.formatter import DateTime +from vit.util import unicode_len # TODO: Remove this once tasklib bug is fixed. from tasklib.serializing import SerializingObject @@ -13,6 +14,6 @@ # https://github.com/robgolding/tasklib/issues/30 dt = dt if isinstance(dt, datetime.datetime) else serializer.timestamp_deserializer(dt) formatted_date = dt.strftime(self.custom_formatter or self.formatter.report) - return (len(formatted_date), (self.colorize(dt), formatted_date)) + return (unicode_len(formatted_date), (self.colorize(dt), formatted_date)) def colorize(self, dt=None): return self.colorizer.uda_date(self.column, dt) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/uda_duration.py new/vit-2.3.0/vit/formatter/uda_duration.py --- old/vit-2.2.0/vit/formatter/uda_duration.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/uda_duration.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,9 +1,10 @@ from vit.formatter import String +from vit.util import unicode_len class UdaDuration(String): def format(self, duration, task): if not duration: return self.markup_none(self.colorize()) - return (len(duration), self.markup_element(duration)) + return (unicode_len(duration), self.markup_element(duration)) def colorize(self, duration=None): return self.colorizer.uda_duration(self.column, duration) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/uda_indicator.py new/vit-2.3.0/vit/formatter/uda_indicator.py --- old/vit-2.2.0/vit/formatter/uda_indicator.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/uda_indicator.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,4 +1,5 @@ from vit.formatter import Formatter +from vit.util import unicode_len class UdaIndicator(Formatter): def format(self, value, task): @@ -6,7 +7,7 @@ return self.markup_none(self.colorize()) else: indicator = self.formatter.indicator_uda[self.column] - return (len(indicator), (self.colorize(value), indicator)) + return (unicode_len(indicator), (self.colorize(value), indicator)) def colorize(self, value=None): return self.colorizer.uda_indicator(self.column, value) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/uda_numeric.py new/vit-2.3.0/vit/formatter/uda_numeric.py --- old/vit-2.2.0/vit/formatter/uda_numeric.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/uda_numeric.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,10 +1,11 @@ from vit.formatter import Number +from vit.util import unicode_len class UdaNumeric(Number): def format(self, number, task): if number is None: return self.markup_none(self.colorize()) number = str(number) - return (len(number), self.markup_element(number)) + return (unicode_len(number), self.markup_element(number)) def colorize(self, number=None): return self.colorizer.uda_numeric(self.column, number) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter/uda_string.py new/vit-2.3.0/vit/formatter/uda_string.py --- old/vit-2.2.0/vit/formatter/uda_string.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/formatter/uda_string.py 2022-10-15 01:05:54.000000000 +0200 @@ -1,9 +1,10 @@ from vit.formatter import String +from vit.util import unicode_len class UdaString(String): def format(self, string, task): if not string: return self.markup_none(self.colorize()) - return (len(string), self.markup_element(string)) + return (unicode_len(string), self.markup_element(string)) def colorize(self, string=None): return self.colorizer.uda_string(self.column, string) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/formatter_base.py new/vit-2.3.0/vit/formatter_base.py --- old/vit-2.2.0/vit/formatter_base.py 2022-03-23 00:27:36.000000000 +0100 +++ new/vit-2.3.0/vit/formatter_base.py 2023-04-14 06:10:49.000000000 +0200 @@ -7,6 +7,7 @@ from vit import util from vit import uda +from vit.util import unicode_len INDICATORS = [ 'active', @@ -18,7 +19,7 @@ DEFAULT_DESCRIPTION_TRUNCATE_LEN=20 -class FormatterBase(object): +class FormatterBase: def __init__(self, loader, config, task_config, markers, task_colorizer): self.loader = loader self.config = config @@ -89,12 +90,12 @@ def format_subproject_indented(self, project_parts): if len(project_parts) == 1: subproject = project_parts[0] - return (len(subproject), '', '', subproject) + return (unicode_len(subproject), '', '', subproject) else: subproject = project_parts.pop() space_padding = (len(project_parts) * 2) - 1 indicator = u'\u21aa ' - width = space_padding + len(indicator) + len(subproject) + width = space_padding + unicode_len(indicator) + unicode_len(subproject) return (width, ' ' * space_padding , indicator, subproject) def recalculate_due_datetimes(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/help.py new/vit-2.3.0/vit/help.py --- old/vit-2.2.0/vit/help.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/help.py 2023-04-14 06:10:49.000000000 +0200 @@ -3,6 +3,7 @@ import urwid from vit.base_list_box import BaseListBox +from vit.util import unicode_len CURLY_BRACES_REGEX = re.compile("[{}]") SPECIAL_KEY_SUBSTITUTIONS = { @@ -57,8 +58,8 @@ 'keys': 0, } for entry in entries: - type_len = len(entry[0]) - keys_len = len(entry[1]) + type_len = unicode_len(entry[0]) + keys_len = unicode_len(entry[1]) if type_len > column_widths['type']: column_widths['type'] = type_len if keys_len > column_widths['keys']: @@ -78,7 +79,7 @@ def eat_other_keybindings(self): return True -class Help(object): +class Help: """Generates help list/display. """ def __init__(self, keybinding_parser, actions, event=None, request_reply=None, action_manager=None): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/key_cache.py new/vit-2.3.0/vit/key_cache.py --- old/vit-2.2.0/vit/key_cache.py 2020-12-30 20:07:59.000000000 +0100 +++ new/vit-2.3.0/vit/key_cache.py 2023-04-14 06:10:49.000000000 +0200 @@ -3,7 +3,7 @@ class KeyCacheError(Exception): pass -class KeyCache(object): +class KeyCache: def __init__(self, keybindings): self.keybindings = keybindings self.cached_keys = '' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/keybinding_parser.py new/vit-2.3.0/vit/keybinding_parser.py --- old/vit-2.2.0/vit/keybinding_parser.py 2020-12-30 20:10:59.000000000 +0100 +++ new/vit-2.3.0/vit/keybinding_parser.py 2023-04-14 06:10:49.000000000 +0200 @@ -23,7 +23,7 @@ class KeybindingError(Exception): pass -class KeybindingParser(object): +class KeybindingParser: def __init__(self, loader, config, action_registry): self.loader = loader self.config = config diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/list_batcher.py new/vit-2.3.0/vit/list_batcher.py --- old/vit-2.2.0/vit/list_batcher.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/list_batcher.py 2023-04-14 06:10:49.000000000 +0200 @@ -3,7 +3,7 @@ class ListBatchError(Exception): pass -class ListBatcher(object): +class ListBatcher: def __init__(self, batch_from, batch_to, batch_to_formatter=None, default_batch_size=DEFAULT_BATCH_SIZE): self.batch_from = batch_from self.batch_to = batch_to diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/loader.py new/vit-2.3.0/vit/loader.py --- old/vit-2.2.0/vit/loader.py 2021-11-04 14:51:45.000000000 +0100 +++ new/vit-2.3.0/vit/loader.py 2023-04-14 06:10:49.000000000 +0200 @@ -8,7 +8,7 @@ DEFAULT_VIT_DIR = '~/.vit' -class Loader(object): +class Loader: def __init__(self): self.user_config_dir = os.path.expanduser('VIT_DIR' in env.user and env.user['VIT_DIR'] or DEFAULT_VIT_DIR) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/markers.py new/vit-2.3.0/vit/markers.py --- old/vit-2.2.0/vit/markers.py 2021-04-24 02:12:31.000000000 +0200 +++ new/vit-2.3.0/vit/markers.py 2023-04-14 06:10:49.000000000 +0200 @@ -33,7 +33,7 @@ 'until.label': '(U)', } -class Markers(object): +class Markers: def __init__(self, config, task_config): self.config = config self.task_config = task_config diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/option_parser.py new/vit-2.3.0/vit/option_parser.py --- old/vit-2.2.0/vit/option_parser.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/option_parser.py 2023-01-06 13:19:59.000000000 +0100 @@ -1,3 +1,4 @@ +import glob import sys import argparse @@ -34,12 +35,21 @@ action="store_true", help="list all available actions", ) +parser.add_argument('--list-pids', + dest="list_pids", + default=False, + action="store_true", + help="list all pids found in pid_dir, if configured", +) def parse_options(): options, filters = parser.parse_known_args() if options.list_actions: list_actions() sys.exit(0) + elif options.list_pids: + ret = list_pids() + sys.exit(ret) return options, filters def format_dictionary_list(item, description): @@ -53,3 +63,29 @@ actions = Actions(action_registry) actions.register() any(format_dictionary_list(action, data['description']) for action, data in actions.get().items()) + +def _get_pids_from_pid_dir(pid_dir): + filepaths = glob.glob("%s/*.pid" % pid_dir) + pids = [] + for filepath in filepaths: + try: + with open(filepath, 'r') as f: + pids.append(f.read()) + except IOError: + pass + return pids + +def list_pids(): + from vit.loader import Loader + from vit.config_parser import ConfigParser + from vit.pid_manager import PidManager + loader = Loader() + config = ConfigParser(loader) + pid_manager = PidManager(config) + if pid_manager.pid_dir: + pids = _get_pids_from_pid_dir(pid_manager.pid_dir) + print("\n".join(pids)) + return 0 + else: + print("ERROR: No pid_dir configured") + return 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/pid_manager.py new/vit-2.3.0/vit/pid_manager.py --- old/vit-2.2.0/vit/pid_manager.py 1970-01-01 01:00:00.000000000 +0100 +++ new/vit-2.3.0/vit/pid_manager.py 2023-04-14 06:10:49.000000000 +0200 @@ -0,0 +1,49 @@ +import os +import errno + +class PidManager: + """Simple process ID manager. + """ + def __init__(self, config): + self.config = config + self.uid = os.getuid() + self.pid = os.getpid() + self._format_pid_dir() + self._make_pid_filepath() + + def setup(self): + if self.pid_dir: + self._create_pid_dir() + self._write_pid_file() + + def teardown(self): + if self.pid_dir: + try: + os.remove(self.pid_file) + # TODO: This needs a little more work to skip errors when no PID file + # exists. + #except OSError as e: + # if e.errno != errno.ENOENT: + # raise OSError("could not remove pid file %s" % self.pid_file) + except: + pass + + def _format_pid_dir(self): + config_pid_dir = self.config.get('vit', 'pid_dir') + self.pid_dir = config_pid_dir.replace("$UID", str(self.uid)) + + def _make_pid_filepath(self): + self.pid_file = "%s/%s.pid" % (self.pid_dir, self.pid) + + def _create_pid_dir(self): + try: + os.makedirs(self.pid_dir, exist_ok=True) + except OSError: + raise OSError("could not create pid_dir %s" % self.pid_dir) + + def _write_pid_file(self): + try: + with open(self.pid_file, "w") as f: + f.write(str(self.pid)) + except IOError: + raise IOError("could not write pid file %s" % self.pid_file) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/process.py new/vit-2.3.0/vit/process.py --- old/vit-2.2.0/vit/process.py 2021-11-04 14:51:45.000000000 +0100 +++ new/vit-2.3.0/vit/process.py 2023-04-14 06:10:49.000000000 +0200 @@ -8,7 +8,7 @@ DEFAULT_CONFIRM = 'Press Enter to continue...' -class Command(object): +class Command: def __init__(self, config): self.config = config diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/readline.py new/vit-2.3.0/vit/readline.py --- old/vit-2.2.0/vit/readline.py 2021-11-04 14:51:45.000000000 +0100 +++ new/vit-2.3.0/vit/readline.py 2023-04-14 06:10:49.000000000 +0200 @@ -1,7 +1,7 @@ import string import re -class Readline(object): +class Readline: def __init__(self, edit_obj): self.edit_obj = edit_obj word_chars = string.ascii_letters + string.digits + "_" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/registry.py new/vit-2.3.0/vit/registry.py --- old/vit-2.2.0/vit/registry.py 2020-12-24 04:52:45.000000000 +0100 +++ new/vit-2.3.0/vit/registry.py 2023-04-14 06:10:49.000000000 +0200 @@ -1,6 +1,6 @@ import uuid -class ActionRegistrar(object): +class ActionRegistrar: def __init__(self, registry): self.registry = registry self.uuid = uuid.uuid4() @@ -17,7 +17,7 @@ def actions(self): return self.registry.get_registered(self.uuid) -class ActionRegistry(object): +class ActionRegistry: def __init__(self): self.actions = {} self.noop_action_name = 'NOOP' @@ -48,7 +48,7 @@ def noop(self): pass -class RequestReply(object): +class RequestReply: def __init__(self): self.handlers = {} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/task.py new/vit-2.3.0/vit/task.py --- old/vit-2.2.0/vit/task.py 2021-11-04 14:51:57.000000000 +0100 +++ new/vit-2.3.0/vit/task.py 2023-04-14 06:10:49.000000000 +0200 @@ -8,7 +8,7 @@ from vit import util from vit.exception import VitException -class TaskListModel(object): +class TaskListModel: def __init__(self, task_config, reports, report=None, data_location=None): if not data_location: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/task_list.py new/vit-2.3.0/vit/task_list.py --- old/vit-2.2.0/vit/task_list.py 2021-07-15 18:06:41.000000000 +0200 +++ new/vit-2.3.0/vit/task_list.py 2023-04-14 06:10:49.000000000 +0200 @@ -13,12 +13,14 @@ from vit.base_list_box import BaseListBox from vit.list_batcher import ListBatcher from vit.formatter.project import Project as ProjectFormatter +from vit.util import unicode_len + REDUCE_COLUMN_WIDTH_LIMIT = 20 COLUMN_PADDING = 2 MARKER_COLUMN_NAME = 'markers' -class TaskTable(object): +class TaskTable: def __init__(self, config, task_config, formatter, screen, on_select=None, event=None, action_manager=None, request_reply=None, markers=None, draw_screen_callback=None): self.config = config @@ -151,10 +153,14 @@ position = self.listbox.focus_position self.list_walker[position].row.set_attr_map({None: attr}) - def flash_focus(self, repeat_times=2, pause_seconds=0.1): + def flash_focus(self, repeat_times=None, pause_seconds=None): + if repeat_times is None: + repeat_times = self.config.get_flash_focus_repeat_times() + if pause_seconds is None: + pause_seconds = self.config.get_flash_focus_pause_seconds() if self.listbox.focus: position = self.listbox.focus_position if self.listbox.focus_position is not None else self.listbox.previous_focus_position if self.listbox.previous_focus_position is not None else None - if position is not None: + if position is not None and repeat_times > 0: self.update_focus_attr('flash on', position) self.draw_screen() for i in repeat(None, repeat_times): @@ -248,7 +254,7 @@ if isinstance(formatted_value, tuple): return formatted_value else: - width = len(formatted_value) if formatted_value else 0 + width = unicode_len(formatted_value) if formatted_value else 0 return width, formatted_value def subproject_indentable(self): @@ -329,7 +335,7 @@ def reconcile_column_width_for_label(self): for idx, column in enumerate(self.columns): - label_len = len(column['label']) + label_len = unicode_len(column['label']) if column['width'] < label_len: self.columns[idx]['width'] = label_len @@ -388,7 +394,7 @@ if grew > 0: self.batcher.add(grew) -class TaskRow(): +class TaskRow: def __init__(self, task, data, alt_row): self.task = task self.data = data @@ -396,7 +402,7 @@ self.uuid = self.task['uuid'] self.id = self.task['id'] -class ProjectRow(): +class ProjectRow: def __init__(self, project, placeholder, alt_row): self.project = project self.placeholder = placeholder diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/util.py new/vit-2.3.0/vit/util.py --- old/vit-2.2.0/vit/util.py 2021-11-04 14:51:45.000000000 +0100 +++ new/vit-2.3.0/vit/util.py 2022-12-25 21:06:26.000000000 +0100 @@ -4,6 +4,8 @@ import shlex from functools import reduce +from urwid.str_util import calc_width + curses.setupterm() e3_seq = curses.tigetstr('E3') or b'' clear_screen_seq = curses.tigetstr('clear') or b'' @@ -55,3 +57,6 @@ def file_readable(filepath): return os.path.isfile(filepath) and os.access(filepath, os.R_OK) + +def unicode_len(string): + return calc_width(string, 0, len(string)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit/version.py new/vit-2.3.0/vit/version.py --- old/vit-2.2.0/vit/version.py 2022-04-18 00:51:26.000000000 +0200 +++ new/vit-2.3.0/vit/version.py 2023-04-14 06:10:49.000000000 +0200 @@ -1 +1 @@ -VIT = '2.2.0' +VIT = '2.3.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit.egg-info/PKG-INFO new/vit-2.3.0/vit.egg-info/PKG-INFO --- old/vit-2.2.0/vit.egg-info/PKG-INFO 2022-04-18 01:07:49.000000000 +0200 +++ new/vit-2.3.0/vit.egg-info/PKG-INFO 2023-04-14 06:27:09.000000000 +0200 @@ -1,13 +1,11 @@ Metadata-Version: 2.1 Name: vit -Version: 2.2.0 +Version: 2.3.0 Summary: Visual Interactive Taskwarrior full-screen terminal interface Home-page: https://github.com/vit-project/vit Author: Chad Phillips Author-email: c...@apartmentlines.com -License: UNKNOWN Keywords: taskwarrior,console,tui,text-user-interface -Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: MIT License Classifier: Environment :: Console @@ -43,7 +41,7 @@ ## Requirements * [Taskwarrior](https://taskwarrior.org) - * [Python](https://www.python.org) 3.5+ + * [Python](https://www.python.org) 3.7+ * [pip](https://pypi.org/project/pip) ## Installation @@ -85,5 +83,3 @@ Taskwarrior Team supp...@taskwarrior.org - - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vit-2.2.0/vit.egg-info/SOURCES.txt new/vit-2.3.0/vit.egg-info/SOURCES.txt --- old/vit-2.2.0/vit.egg-info/SOURCES.txt 2022-04-18 01:07:49.000000000 +0200 +++ new/vit-2.3.0/vit.egg-info/SOURCES.txt 2023-04-14 06:27:09.000000000 +0200 @@ -37,6 +37,7 @@ vit/markers.py vit/multi_widget.py vit/option_parser.py +vit/pid_manager.py vit/process.py vit/readline.py vit/registry.py