-
Notifications
You must be signed in to change notification settings - Fork 80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create interactive command for curses interface (for discussion purposes) #66
base: main
Are you sure you want to change the base?
Conversation
First bug found: if I create a new item, it defaults to completed, without regard to the information the user provided in the editor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Running todoman interactive
just shows me the statusbar on top that says Todoman
, and nothing else on screen.
I'll try too look at it closer and find out what's going on when I get a bit more time.
The code looks tidy enough, though I've left some minor comments.
todoman/interactive.py
Outdated
|
||
logger = logging.getLogger() | ||
|
||
class TodomanItem(urwid.CheckBox): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TodoItem
might be more descriptive.
As for other classes, prefixing Todoman
to their name sounds redundant (especially since they're already in package todoman.interactive
.
todoman/interactive.py
Outdated
TodomanItem (i.e. self) into a string representation that is suitable | ||
for its context. | ||
|
||
(TodomanItem, str, Database, function) -> None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're using sphinx-like doc for param/return types:
http://www.sphinx-doc.org/en/stable/domains.html#info-field-lists
It's nitpicky of me, but let's keep it consistent.
todoman/interactive.py
Outdated
if key == 'l': | ||
self.list_chooser() | ||
return None | ||
if key == 'm': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use elif
in all these and avoid all the extra return None
lines.
(TodomanInteractive, str) -> None | ||
''' | ||
if key in ('q', 'Q'): | ||
raise urwid.ExitMainLoop() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When editing an entry, if a checkbox if focus and I press q, it immediately exists, without any confirmation, etc.
todoman/model.py
Outdated
@@ -237,7 +237,7 @@ class Database: | |||
def __init__(self, path): | |||
self.path = path | |||
|
|||
@cached_property | |||
@property |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll need to manually cache this to avoid the extra overhead.
That is:
- Set
_cached_todos
on first read. - Manually add new entries there.
- Manually remove entries from there.
- Return
_cached_todos
if it's defined, otherwise cached all of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was done to keep the database fresh even after editing an item. Let's make todos
not a property, but instead rename it to get_todos
with a skip_cache
parameter.
Also, please rebase onto |
I'd prefer if the interactive interface does deletions and marking things as done upon exiting the interface, so I can hit |
todoman/cli.py
Outdated
Provide an interactive, curses-based interface to Todoman. | ||
''' | ||
TodomanInteractive(ctx.obj['db'].values(), ctx.obj['formatter']) | ||
ctx.exit(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why always exit with 1?
A default callback function to use when closing the previous page. | ||
This callback function handles several keywords that are generic to | ||
all callbacks. If a certain keyword is not set, the method does | ||
nothing with it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think pages could be implemented much more easily, without subclassing. You'd need a wrapper widget that catches Esc
(so just let the keypress event bubble up from the page's handler in that case), and that widget maintains a stack (list) of pages that it appends and pops from. If the list is empty, ExitMainLoop
is raised.
todoman/interactive.py
Outdated
|
||
(TodomanItemListPage, **kwargs) -> None | ||
''' | ||
for key, value in kwargs.items(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, why not just have database
as part of the method signature?
todoman/interactive.py
Outdated
''' | ||
self.filename = filename | ||
self.database = database | ||
try: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might as well have the params database, todo
instead of database, filename
and avoid this logic.
items = [] | ||
for filename in self.database.todos.keys(): | ||
todo = TodomanItem(filename, self.database, self.generate_label) | ||
if not self.done_is_hidden or not todo.is_completed: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should reuse the logic from the CLI here, but not sure how.
todoman/interactive.py
Outdated
newPage = TodomanDatabasesPage(self.parent, self.callback_copy_to) | ||
self.open_page(newPage) | ||
|
||
def delete(self, item = None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please no spaces between =
when part of parameters.
todoman/interactive.py
Outdated
|
||
(TodomanItemListPage, TodomanItem) -> str | ||
''' | ||
return "{0} {1}".format('!' if item.has_priority else ' ', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have todoman.ui.TodoFormatter
for this (which will also add colors)
from .interactive import TodomanInteractive | ||
|
||
import logging | ||
logging.basicConfig() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basicConfig
should be called in the cli
function, not at module level. In the case of showing a UI you won't want to output anything I think.
|
||
import logging | ||
logging.basicConfig() | ||
logger = logging.getLogger() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use logging.getLogger(__name__)
to namespace your logging.
todoman/interactive.py
Outdated
|
||
(TodomanPage, **kwargs) -> None | ||
''' | ||
for key, value in kwargs.items(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it invalid to call this callback with anything but statusbar
?
- If yes:
def callback(self, statusbar)
- If no:
def callback(self, statusbar, **kwargs)
I generally feel like a lot of classes are unnecessarily prefixed by |
@Pieter1024 it's been a while. Are you still willing to work on this? |
Sorry, I really broke this PR with some substantial changes to |
ad2b0f5
to
a2b1aac
Compare
I've rebased this to master and fixed style violations, but haven't really looked at the code more closely. Since @Pieter1024 hasn't responded back, will probably be picking this up in the coming days, if time allows. |
I'll make a separate PR for the logging. |
See #106 |
@@ -530,7 +530,8 @@ def __init__(self, databases, formatter): | |||
Create a Main instance based on the Database objects that | |||
the regular Todoman cli module passes. | |||
|
|||
(Main, [Database], TodoFormatter) -> None | |||
:type databases: list[Database] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need to change this, because with the current model it doesn't make sense to pass a list of Database
s.
''' | ||
return self.todo.priority not in [None, 0] | ||
return self.todo.priority not in (None, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's time to implement Todo.is_urgent
as a wrapper around this, so we stop duplicating todo.priority not in [None, 0]
al over the place.
Also, I think it can never be None, because of how the getter is written.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove this if we use the normal formatter instead of generate_label
@@ -167,7 +156,9 @@ def __init__(self, parent, callback, database): | |||
is responsible for passing information to the underlying Page. | |||
It is usually a method of the underlying Page. | |||
|
|||
(ItemListPage, Main, function, Database) -> None | |||
:param parent: Main |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not the right format, it's :param TYPE NAME
, for example :param int amount
.
Thanks. I also think we should shuffle packages around:
I think this should end up in:
|
Move |
I just tried to refactor and bumped into a few design limits of the current datamodel |
Proposal seems great. Do these limitations block the refactor? Or are they for future reference? |
I think so, the API isn't very pleasing for this usage. Particularly the split between |
Maybe I'll have to revisit that. Having a separate class keeps the door open for separate storage types (will we ever want that!?). And I think is a good abstraction. Maybe having the |
This is my first attempt at a curses-based interface for Todoman. I would like your feedback on what I've written so far. I am aware that it is by no means done, but before I start polishing it I would like to know I'm polishing the right thing. This PR is in reference to issue #62.
To run:
todo interactive
In the database listing view (the one you see when starting the program), the following commands are available:
l
: ListChooser, choose a different database from the available onese
: Edit, edit the currently highlighted itemn
: New, create a new item in this databasem
: Move, move the currently highlighted item to a databasec
: Copy, copy the currently highlighted item to a databased
: Delete, delete the currently highlighted itemD
: Delete all completed, delete all tasks in the database that are completedh
: Hide or show completed tasks in the databasej
andk
are like 'up' and 'down', like in vimspace
orenter
toggles the currently highlighted itemIn all screens,
esc
closes the current screen and returns you to the previous one.I have one specific issue on which I would appreciate your views. When I create a new item, the current database should be updated. However, you defined the todos property of Database as a cached_property, which prevents this. I have, for now, changed the cached_property into a property, but I have already noticed this is a severe performance impairment. Could we resolve this situation in a neater way? E.g., could we explicitly add the new item to the cached database?
The following definitely still needs to be done:
I would also like to make a page that shows all upcoming tasks across databases.