twd-m4sc0 3.1.0__tar.gz → 3.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/PKG-INFO +1 -1
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/pyproject.toml +5 -1
- twd_m4sc0-3.1.2/src/twd/__init__.py +1 -0
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/src/twd/cli.py +35 -7
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/src/twd/config.py +19 -1
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/src/twd/data.py +48 -9
- twd_m4sc0-3.1.2/src/twd/modals/__init__.py +4 -0
- twd_m4sc0-3.1.0/src/twd/modals.py → twd_m4sc0-3.1.2/src/twd/modals/confirm.py +20 -15
- twd_m4sc0-3.1.2/src/twd/modals/edit.py +52 -0
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/src/twd/tui.py +51 -24
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/src/twd_m4sc0.egg-info/PKG-INFO +1 -1
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/src/twd_m4sc0.egg-info/SOURCES.txt +3 -1
- twd_m4sc0-3.1.0/src/twd/__init__.py +0 -6
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/README.md +0 -0
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/setup.cfg +0 -0
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/src/twd/tui.tcss +0 -0
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/src/twd/utils.py +0 -0
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/src/twd_m4sc0.egg-info/dependency_links.txt +0 -0
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/src/twd_m4sc0.egg-info/entry_points.txt +0 -0
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/src/twd_m4sc0.egg-info/requires.txt +0 -0
- {twd_m4sc0-3.1.0 → twd_m4sc0-3.1.2}/src/twd_m4sc0.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "twd-m4sc0"
|
|
3
|
-
version = "3.1.0"
|
|
3
|
+
# version = "3.1.0"
|
|
4
|
+
dynamic = ["version"]
|
|
4
5
|
description = "TWD by m4sc0"
|
|
5
6
|
authors = [{name = "m4sc0"}]
|
|
6
7
|
requires-python = ">=3.13"
|
|
@@ -32,3 +33,6 @@ where = ["src"]
|
|
|
32
33
|
|
|
33
34
|
[tool.setuptools.package-data]
|
|
34
35
|
twd = ["*.tcss"]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.dynamic]
|
|
38
|
+
version = {attr = "twd.__version__"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.1.2"
|
|
@@ -8,7 +8,7 @@ from .tui import TWDApp
|
|
|
8
8
|
|
|
9
9
|
@click.group(invoke_without_command=True)
|
|
10
10
|
@click.pass_context
|
|
11
|
-
@click.version_option(version=__version__)
|
|
11
|
+
@click.version_option(version=__version__, prog_name="twd")
|
|
12
12
|
def cli(ctx):
|
|
13
13
|
"""
|
|
14
14
|
TWD - Temp / Tracked Working Directory
|
|
@@ -16,7 +16,7 @@ def cli(ctx):
|
|
|
16
16
|
ctx.ensure_object(dict)
|
|
17
17
|
|
|
18
18
|
ctx.obj['config'] = Config.load() # load config
|
|
19
|
-
ctx.obj['manager'] = TwdManager(ctx.obj['config']
|
|
19
|
+
ctx.obj['manager'] = TwdManager(ctx.obj['config']) # pass config
|
|
20
20
|
|
|
21
21
|
# if no subcommand was provided, launch TUI
|
|
22
22
|
if ctx.invoked_subcommand is None:
|
|
@@ -39,7 +39,18 @@ def cli(ctx):
|
|
|
39
39
|
@click.pass_context
|
|
40
40
|
def save(ctx, path, alias, name):
|
|
41
41
|
"""
|
|
42
|
-
Save a new
|
|
42
|
+
Save a new TWD with an Alias (UUID like) and Name
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
|
|
46
|
+
twd save ./Movies movies Media
|
|
47
|
+
|
|
48
|
+
twd save / root "Root Directory -- /dev/sda3"
|
|
49
|
+
|
|
50
|
+
twd save . finances-q1 "Finance Documents Q1"
|
|
51
|
+
|
|
52
|
+
The naming conventions for the alias align with kebab-casing.
|
|
53
|
+
There are no naming conventions for the name.
|
|
43
54
|
"""
|
|
44
55
|
manager: TwdManager = ctx.obj['manager']
|
|
45
56
|
|
|
@@ -61,7 +72,13 @@ def save(ctx, path, alias, name):
|
|
|
61
72
|
@click.pass_context
|
|
62
73
|
def get(ctx, alias):
|
|
63
74
|
"""
|
|
64
|
-
|
|
75
|
+
cd into a directory by the given Alias
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
|
|
79
|
+
twd get movies
|
|
80
|
+
|
|
81
|
+
There's really not much to it.
|
|
65
82
|
"""
|
|
66
83
|
manager = ctx.obj['manager']
|
|
67
84
|
|
|
@@ -81,7 +98,13 @@ def get(ctx, alias):
|
|
|
81
98
|
@click.pass_context
|
|
82
99
|
def remove(ctx, alias):
|
|
83
100
|
"""
|
|
84
|
-
|
|
101
|
+
Remove TWD by the given Alias
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
|
|
105
|
+
twd remove movies
|
|
106
|
+
|
|
107
|
+
That's it.
|
|
85
108
|
"""
|
|
86
109
|
manager = ctx.obj['manager']
|
|
87
110
|
|
|
@@ -95,7 +118,9 @@ def remove(ctx, alias):
|
|
|
95
118
|
@cli.command('list')
|
|
96
119
|
@click.pass_context
|
|
97
120
|
def list_twds(ctx):
|
|
98
|
-
"""
|
|
121
|
+
"""
|
|
122
|
+
List all TWDs
|
|
123
|
+
"""
|
|
99
124
|
manager = ctx.obj['manager']
|
|
100
125
|
|
|
101
126
|
entries = manager.list_all()
|
|
@@ -118,7 +143,10 @@ def list_twds(ctx):
|
|
|
118
143
|
@click.pass_context
|
|
119
144
|
def clean(ctx, yes):
|
|
120
145
|
"""
|
|
121
|
-
|
|
146
|
+
Clean the records of rogue TWDs
|
|
147
|
+
|
|
148
|
+
Sometimes it's possible that TWDs are not properly cleaned up.
|
|
149
|
+
That's what this subcommand does.
|
|
122
150
|
"""
|
|
123
151
|
manager = ctx.obj['manager']
|
|
124
152
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
from pydantic import BaseModel, Field, validator
|
|
4
|
+
from enum import Enum
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
@@ -32,10 +32,28 @@ def get_data_path() -> Path:
|
|
|
32
32
|
|
|
33
33
|
return data_dir / "data.csv"
|
|
34
34
|
|
|
35
|
+
class SortField(str, Enum):
|
|
36
|
+
ALIAS = "alias"
|
|
37
|
+
PATH = "path"
|
|
38
|
+
NAME = "name"
|
|
39
|
+
CREATED_AT = "created_at"
|
|
40
|
+
|
|
41
|
+
class SortDirection(str, Enum):
|
|
42
|
+
ASC = "asc"
|
|
43
|
+
DESC = "desc"
|
|
44
|
+
|
|
45
|
+
class SortOrderSetting(BaseModel):
|
|
46
|
+
field: SortField = SortField.CREATED_AT
|
|
47
|
+
direction: SortDirection = SortDirection.ASC
|
|
48
|
+
|
|
49
|
+
class Settings(BaseModel):
|
|
50
|
+
sort_order: SortOrderSetting = Field(default_factory=SortOrderSetting)
|
|
51
|
+
|
|
35
52
|
class Config(BaseModel):
|
|
36
53
|
"""App configuration"""
|
|
37
54
|
|
|
38
55
|
data_path: Path = Field(default_factory=get_data_path)
|
|
56
|
+
settings: Settings = Field(default_factory=Settings)
|
|
39
57
|
|
|
40
58
|
@validator("data_path")
|
|
41
59
|
def validate_path(cls, v):
|
|
@@ -5,7 +5,7 @@ from pydantic import BaseModel, Field, validator
|
|
|
5
5
|
from typing import List, Optional
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
|
|
8
|
-
from .config import Config
|
|
8
|
+
from .config import Config, SortDirection
|
|
9
9
|
|
|
10
10
|
class Entry(BaseModel):
|
|
11
11
|
"""Data class for a signle TWD Entry"""
|
|
@@ -15,6 +15,17 @@ class Entry(BaseModel):
|
|
|
15
15
|
name: str = Field(..., min_length=3)
|
|
16
16
|
created_at: datetime = Field(default_factory=datetime.now)
|
|
17
17
|
|
|
18
|
+
def __eq__(self, other) -> bool:
|
|
19
|
+
"""Compare entries based on their values"""
|
|
20
|
+
if not isinstance(other, Entry):
|
|
21
|
+
return NotImplemented
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
self.alias == other.alias
|
|
25
|
+
and self.path == other.path
|
|
26
|
+
and self.name == other.name
|
|
27
|
+
)
|
|
28
|
+
|
|
18
29
|
@validator("alias")
|
|
19
30
|
def validate_alias(cls, v):
|
|
20
31
|
if not v.replace("_", "").replace("-", "").isalnum():
|
|
@@ -47,23 +58,33 @@ class Entry(BaseModel):
|
|
|
47
58
|
created_at=datetime.fromisoformat(row[3])
|
|
48
59
|
)
|
|
49
60
|
|
|
61
|
+
@classmethod
|
|
62
|
+
def from_values(cls, alias, path, name, created_at) -> "Entry":
|
|
63
|
+
"""create from values"""
|
|
64
|
+
return cls(
|
|
65
|
+
alias=alias,
|
|
66
|
+
path=path,
|
|
67
|
+
name=name,
|
|
68
|
+
created_at=created_at
|
|
69
|
+
)
|
|
70
|
+
|
|
50
71
|
class TwdManager:
|
|
51
72
|
"""twd entry manager stored in csv"""
|
|
52
73
|
|
|
53
74
|
CSV_HEADERS = ["alias", "path", "name", "created_at"]
|
|
54
75
|
CSV_HEADERS_FANCY = ["Alias", "Path", "Description", "Created at"]
|
|
55
76
|
|
|
56
|
-
def __init__(self,
|
|
57
|
-
self.
|
|
77
|
+
def __init__(self, config: Config): # accept config as argument
|
|
78
|
+
self.config = config
|
|
58
79
|
self._ensure_csv_exists()
|
|
59
80
|
self.cwd = str(Path.cwd())
|
|
60
81
|
|
|
61
82
|
def _ensure_csv_exists(self) -> None:
|
|
62
83
|
"""create csv headers"""
|
|
63
|
-
if not self.
|
|
64
|
-
self.
|
|
84
|
+
if not self.config.data_path.exists():
|
|
85
|
+
self.config.data_path.parent.mkdir(parents=True, exist_ok=True)
|
|
65
86
|
|
|
66
|
-
with open(self.
|
|
87
|
+
with open(self.config.data_path, 'w', newline='') as f:
|
|
67
88
|
writer = csv.writer(f)
|
|
68
89
|
writer.writerow(self.CSV_HEADERS)
|
|
69
90
|
|
|
@@ -71,7 +92,7 @@ class TwdManager:
|
|
|
71
92
|
"""read all entries"""
|
|
72
93
|
entries = []
|
|
73
94
|
|
|
74
|
-
with open(self.
|
|
95
|
+
with open(self.config.data_path, 'r', newline='') as f:
|
|
75
96
|
reader = csv.reader(f)
|
|
76
97
|
|
|
77
98
|
next(reader) # skip headers
|
|
@@ -82,7 +103,7 @@ class TwdManager:
|
|
|
82
103
|
return entries
|
|
83
104
|
|
|
84
105
|
def _write_all(self, entries: List[Entry]) -> None:
|
|
85
|
-
with open(self.
|
|
106
|
+
with open(self.config.data_path, 'w', newline='') as f:
|
|
86
107
|
writer = csv.writer(f)
|
|
87
108
|
writer.writerow(self.CSV_HEADERS)
|
|
88
109
|
|
|
@@ -115,6 +136,22 @@ class TwdManager:
|
|
|
115
136
|
|
|
116
137
|
return None
|
|
117
138
|
|
|
139
|
+
def update(self, alias: str, entry: Entry) -> bool:
|
|
140
|
+
"""update TWD by alias"""
|
|
141
|
+
if not self.exists(alias):
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
entries = self._read_all()
|
|
145
|
+
|
|
146
|
+
# find and upate
|
|
147
|
+
for i, e in enumerate(entries):
|
|
148
|
+
if e.alias == alias.lower():
|
|
149
|
+
entries[i] = entry
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
self._write_all(entries)
|
|
153
|
+
return True
|
|
154
|
+
|
|
118
155
|
def remove(self, alias: str) -> None:
|
|
119
156
|
"""remove entry by alias"""
|
|
120
157
|
entries = self._read_all()
|
|
@@ -130,7 +167,9 @@ class TwdManager:
|
|
|
130
167
|
def list_all(self) -> List[Entry]:
|
|
131
168
|
entries = self._read_all()
|
|
132
169
|
|
|
133
|
-
|
|
170
|
+
sort_config = self.config.settings.sort_order
|
|
171
|
+
reverse = (sort_config.direction == SortDirection.DESC)
|
|
172
|
+
return sorted(entries, key=lambda e: getattr(e, sort_config.field.value), reverse=reverse)
|
|
134
173
|
|
|
135
174
|
def exists(self, alias: str) -> bool:
|
|
136
175
|
return self.get(alias) is not None
|
|
@@ -3,11 +3,13 @@ from textual.app import ComposeResult
|
|
|
3
3
|
from textual.screen import ModalScreen
|
|
4
4
|
from textual.containers import Container, Horizontal
|
|
5
5
|
from textual.widgets import Button, Label
|
|
6
|
-
from typing import Union
|
|
6
|
+
from typing import Union, TypeVar, Generic
|
|
7
7
|
|
|
8
8
|
from twd.data import Entry
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
T = TypeVar('T')
|
|
11
|
+
|
|
12
|
+
class ConfirmModal(ModalScreen[T], Generic[T]):
|
|
11
13
|
"""A confirm modal"""
|
|
12
14
|
|
|
13
15
|
DEFAULT_CSS = """
|
|
@@ -42,7 +44,9 @@ class ConfirmModal(ModalScreen[bool]):
|
|
|
42
44
|
self,
|
|
43
45
|
message: Union[str, None] = None,
|
|
44
46
|
confirm_text: str = "Yes",
|
|
45
|
-
cancel_text: str = "No"
|
|
47
|
+
cancel_text: str = "No",
|
|
48
|
+
confirm_value: T | None = None,
|
|
49
|
+
cancel_value: T | None = None,
|
|
46
50
|
):
|
|
47
51
|
"""
|
|
48
52
|
message: The message to display when popping the modal
|
|
@@ -53,6 +57,9 @@ class ConfirmModal(ModalScreen[bool]):
|
|
|
53
57
|
self.confirm_text = confirm_text
|
|
54
58
|
self.cancel_text = cancel_text
|
|
55
59
|
|
|
60
|
+
self.confirm_value = confirm_value
|
|
61
|
+
self.cancel_value = cancel_value
|
|
62
|
+
|
|
56
63
|
super().__init__()
|
|
57
64
|
|
|
58
65
|
def compose_content(self) -> ComposeResult:
|
|
@@ -68,18 +75,16 @@ class ConfirmModal(ModalScreen[bool]):
|
|
|
68
75
|
with Container():
|
|
69
76
|
yield from self.compose_content()
|
|
70
77
|
with Horizontal():
|
|
71
|
-
yield Button(self.cancel_text, id="
|
|
72
|
-
yield Button(self.confirm_text, id="
|
|
73
|
-
|
|
74
|
-
@on(Button.Pressed, "#
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"""decision yes"""
|
|
82
|
-
self.dismiss(True)
|
|
78
|
+
yield Button(self.cancel_text, id="cancel", variant="error")
|
|
79
|
+
yield Button(self.confirm_text, id="confirm", variant="success")
|
|
80
|
+
|
|
81
|
+
@on(Button.Pressed, "#cancel")
|
|
82
|
+
def cancel_pressed(self) -> None:
|
|
83
|
+
self.dismiss(self.cancel_value)
|
|
84
|
+
|
|
85
|
+
@on(Button.Pressed, "#confirm")
|
|
86
|
+
def confirm_pressed(self) -> None:
|
|
87
|
+
self.dismiss(self.confirm_value)
|
|
83
88
|
|
|
84
89
|
class EntryDeleteModal(ConfirmModal):
|
|
85
90
|
"""Confirmation modal with detailed entry information"""
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from textual import on
|
|
2
|
+
from textual.app import ComposeResult
|
|
3
|
+
from textual.containers import Container, Horizontal
|
|
4
|
+
from textual.widgets import Button, Label, Input
|
|
5
|
+
|
|
6
|
+
from twd.data import Entry
|
|
7
|
+
from twd.modals import ConfirmModal
|
|
8
|
+
|
|
9
|
+
class EditEntryModal(ConfirmModal[Entry | None]):
|
|
10
|
+
"""A modal to edit an existing entry"""
|
|
11
|
+
|
|
12
|
+
DEFAULT_CSS = """
|
|
13
|
+
Label {
|
|
14
|
+
width: 100%;
|
|
15
|
+
text-align: left;
|
|
16
|
+
}
|
|
17
|
+
Input {
|
|
18
|
+
margin: 1;
|
|
19
|
+
}
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, entry):
|
|
23
|
+
"""
|
|
24
|
+
entry: the entry to edit
|
|
25
|
+
"""
|
|
26
|
+
self.entry = Entry.from_values(entry.alias, entry.path, entry.name, entry.created_at)
|
|
27
|
+
|
|
28
|
+
super().__init__(
|
|
29
|
+
confirm_text="Save",
|
|
30
|
+
cancel_text="Discard",
|
|
31
|
+
confirm_value=self.entry,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def compose_content(self) -> ComposeResult:
|
|
35
|
+
yield Label(f"Edit Entry", id="title")
|
|
36
|
+
|
|
37
|
+
yield Label("Alias")
|
|
38
|
+
yield Input(value=self.entry.alias, id="alias", disabled=True)
|
|
39
|
+
yield Label("Path")
|
|
40
|
+
yield Input(value=str(self.entry.path), id="path")
|
|
41
|
+
yield Label("Name")
|
|
42
|
+
yield Input(value=self.entry.name, id="name")
|
|
43
|
+
|
|
44
|
+
@on(Input.Changed, "#path")
|
|
45
|
+
def on_path_change(self, event: Input.Changed) -> None:
|
|
46
|
+
self.entry.path = event.value
|
|
47
|
+
|
|
48
|
+
@on(Input.Changed, "#name")
|
|
49
|
+
def on_name_change(self, event: Input.Changed) -> None:
|
|
50
|
+
self.entry.name = event.value
|
|
51
|
+
|
|
52
|
+
|
|
@@ -8,9 +8,9 @@ from textual.widgets import Button, Digits, Footer, Header, DataTable, Label, Ru
|
|
|
8
8
|
from textual.color import Color
|
|
9
9
|
|
|
10
10
|
from twd.config import Config
|
|
11
|
-
from twd.data import TwdManager
|
|
11
|
+
from twd.data import TwdManager, Entry
|
|
12
12
|
from twd.utils import fuzzy_search, linear_search
|
|
13
|
-
from twd.modals import ConfirmModal, EntryDeleteModal
|
|
13
|
+
from twd.modals import ConfirmModal, EntryDeleteModal, EditEntryModal
|
|
14
14
|
|
|
15
15
|
class Mode(Enum):
|
|
16
16
|
NORMAL = "normal"
|
|
@@ -29,9 +29,10 @@ class TWDApp(App):
|
|
|
29
29
|
Binding("k", "cursor_up", "Up"),
|
|
30
30
|
|
|
31
31
|
# modify
|
|
32
|
-
Binding("/", "
|
|
33
|
-
Binding("d", "
|
|
34
|
-
Binding("
|
|
32
|
+
Binding("/", "slash_key", "Search"),
|
|
33
|
+
Binding("d", "d_key", "Delete"),
|
|
34
|
+
Binding("e", "e_key", "Edit"),
|
|
35
|
+
Binding("escape", "escape_key", "Normal", show=False),
|
|
35
36
|
# TODO: edit
|
|
36
37
|
# TODO: rename
|
|
37
38
|
|
|
@@ -43,6 +44,7 @@ class TWDApp(App):
|
|
|
43
44
|
]
|
|
44
45
|
|
|
45
46
|
mode: Mode = reactive(Mode.NORMAL)
|
|
47
|
+
search_results = None
|
|
46
48
|
|
|
47
49
|
def __init__(self, manager: TwdManager, *args, **kwargs):
|
|
48
50
|
self.manager = manager
|
|
@@ -98,6 +100,19 @@ class TWDApp(App):
|
|
|
98
100
|
for entry in entries:
|
|
99
101
|
table.add_row(entry.alias, str(entry.path), entry.name, entry.created_at.strftime("%Y-%m-%d %H:%M:%S"))
|
|
100
102
|
|
|
103
|
+
def _current_row_entry(self) -> Entry:
|
|
104
|
+
table = self.query_one(DataTable)
|
|
105
|
+
|
|
106
|
+
# get row
|
|
107
|
+
row_key = table.coordinate_to_cell_key(table.cursor_coordinate).row_key
|
|
108
|
+
row_data = table.get_row(row_key)
|
|
109
|
+
alias = row_data[0]
|
|
110
|
+
|
|
111
|
+
# get entry
|
|
112
|
+
entry = self.manager.get(alias)
|
|
113
|
+
|
|
114
|
+
return entry
|
|
115
|
+
|
|
101
116
|
def watch_mode(self, old_mode: Mode, new_mode: Mode) -> None:
|
|
102
117
|
"""
|
|
103
118
|
react to mode changes
|
|
@@ -153,7 +168,7 @@ class TWDApp(App):
|
|
|
153
168
|
|
|
154
169
|
table.move_cursor(row=prev_row)
|
|
155
170
|
|
|
156
|
-
def
|
|
171
|
+
def action_slash_key(self) -> None:
|
|
157
172
|
"""
|
|
158
173
|
enter search mode
|
|
159
174
|
"""
|
|
@@ -161,30 +176,25 @@ class TWDApp(App):
|
|
|
161
176
|
return
|
|
162
177
|
self.mode = Mode.SEARCH
|
|
163
178
|
|
|
164
|
-
def
|
|
179
|
+
def action_escape_key(self) -> None:
|
|
165
180
|
"""
|
|
166
181
|
enter normal mode
|
|
167
182
|
"""
|
|
168
183
|
if self.mode == Mode.NORMAL:
|
|
184
|
+
if self.search_results is not None:
|
|
185
|
+
self._populate_table()
|
|
186
|
+
self.search_results = None
|
|
169
187
|
return
|
|
170
188
|
self.mode = Mode.NORMAL
|
|
171
189
|
|
|
172
|
-
def
|
|
190
|
+
def action_d_key(self) -> None:
|
|
173
191
|
"""
|
|
174
192
|
open confirm modal and delete entry if yes
|
|
175
193
|
"""
|
|
176
194
|
if not self.mode == Mode.NORMAL:
|
|
177
195
|
return
|
|
178
196
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
# get row
|
|
182
|
-
row_key = table.coordinate_to_cell_key(table.cursor_coordinate).row_key
|
|
183
|
-
row_data = table.get_row(row_key)
|
|
184
|
-
alias = row_data[0]
|
|
185
|
-
|
|
186
|
-
# get entry
|
|
187
|
-
entry = self.manager.get(alias)
|
|
197
|
+
entry = self._current_row_entry()
|
|
188
198
|
|
|
189
199
|
def check_delete(decision: bool | None) -> None:
|
|
190
200
|
"""
|
|
@@ -199,9 +209,29 @@ class TWDApp(App):
|
|
|
199
209
|
|
|
200
210
|
self.notify(f"Removed entry \"{entry.name}\"")
|
|
201
211
|
|
|
202
|
-
# self.push_screen(ConfirmModal(message=f"Are you sure want to delete '{entry.alias}'?"), check_delete)
|
|
203
212
|
self.push_screen(EntryDeleteModal(entry), check_delete)
|
|
204
213
|
|
|
214
|
+
def action_e_key(self) -> None:
|
|
215
|
+
"""
|
|
216
|
+
open edit modal and edit entry in place
|
|
217
|
+
"""
|
|
218
|
+
entry = self._current_row_entry()
|
|
219
|
+
|
|
220
|
+
def save_new_entry(new_entry: Entry | None) -> None:
|
|
221
|
+
if not new_entry or entry == new_entry:
|
|
222
|
+
# user hit 'Discard'
|
|
223
|
+
# no changes so no update
|
|
224
|
+
self.notify(f"No changes")
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
self.notify(f"Updated TWD '{new_entry.alias}'")
|
|
228
|
+
|
|
229
|
+
self.manager.update(new_entry.alias, new_entry)
|
|
230
|
+
self.manager._write_all(self.manager._read_all())
|
|
231
|
+
self._populate_table()
|
|
232
|
+
|
|
233
|
+
self.push_screen(EditEntryModal(entry), save_new_entry)
|
|
234
|
+
|
|
205
235
|
def action_exit(self) -> None:
|
|
206
236
|
self.exit()
|
|
207
237
|
|
|
@@ -228,6 +258,7 @@ class TWDApp(App):
|
|
|
228
258
|
filtered = [item[0] for item in search_result]
|
|
229
259
|
|
|
230
260
|
self._populate_table(filtered)
|
|
261
|
+
self.search_results = filtered
|
|
231
262
|
|
|
232
263
|
@on(Input.Submitted, "#search-input")
|
|
233
264
|
def on_search_submitted(self, e: Input.Submitted) -> None:
|
|
@@ -238,6 +269,7 @@ class TWDApp(App):
|
|
|
238
269
|
return
|
|
239
270
|
|
|
240
271
|
self.mode = Mode.NORMAL
|
|
272
|
+
self._populate_table(self.search_results)
|
|
241
273
|
|
|
242
274
|
self.query_one(DataTable).focus()
|
|
243
275
|
|
|
@@ -250,12 +282,7 @@ class TWDApp(App):
|
|
|
250
282
|
table = event.data_table
|
|
251
283
|
row_key = event.row_key
|
|
252
284
|
|
|
253
|
-
|
|
254
|
-
row_data = table.get_row(row_key)
|
|
255
|
-
alias = row_data[0]
|
|
256
|
-
|
|
257
|
-
# get entry
|
|
258
|
-
entry = self.manager.get(alias)
|
|
285
|
+
entry = self._current_row_entry()
|
|
259
286
|
|
|
260
287
|
self.notify(f"Selected: {entry.alias} -> {entry.path}")
|
|
261
288
|
|
|
@@ -4,10 +4,12 @@ src/twd/__init__.py
|
|
|
4
4
|
src/twd/cli.py
|
|
5
5
|
src/twd/config.py
|
|
6
6
|
src/twd/data.py
|
|
7
|
-
src/twd/modals.py
|
|
8
7
|
src/twd/tui.py
|
|
9
8
|
src/twd/tui.tcss
|
|
10
9
|
src/twd/utils.py
|
|
10
|
+
src/twd/modals/__init__.py
|
|
11
|
+
src/twd/modals/confirm.py
|
|
12
|
+
src/twd/modals/edit.py
|
|
11
13
|
src/twd_m4sc0.egg-info/PKG-INFO
|
|
12
14
|
src/twd_m4sc0.egg-info/SOURCES.txt
|
|
13
15
|
src/twd_m4sc0.egg-info/dependency_links.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|