Skip to content

Commit a782f2d

Browse files
committed
core dabbit
1 parent ab3c6e8 commit a782f2d

6 files changed

Lines changed: 57 additions & 20 deletions

File tree

docs/src/content/docs/dev/features/ipc.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ Every command prints one JSON object. Successful commands return `"ok": true` an
4444
IPC only listens on `127.0.0.1`, and each rovr instance generates a private token. The target instance still checks `settings.ipc.permissions` for every action.<br/>
4545
Permissions can be `allow`, `deny`, or `prompt`; unconfigured actions are denied. Subcommands use keys such as `clipboard.paste` and `tab.close`.
4646

47+
`prompt` is unavailable for `notify`, `ask`, and `input` commands, which always require user interaction.
48+
4749
## Commands
4850

4951
### `cd`
@@ -85,6 +87,8 @@ rovr --ipc clipboard list
8587
}
8688
```
8789

90+
if the user intervenes due to errors, returns with `"ok": false`
91+
8892
`list` returns every clipboard entry with its type and whether it is selected:
8993

9094
```json

src/rovr/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ def cli(argv: list[str] | None = None) -> None:
300300
args.ipc
301301
if args.ipc is not None
302302
else args.ipc_to[1:]
303-
if args.ipc_to[0].isdigit()
303+
if args.ipc_to[0].isdecimal()
304304
else args.ipc_to
305305
)
306306
IPC_PARSER.parse_args(ipc_args)

src/rovr/assets/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ cd = "allow"
153153
quit = "prompt"
154154
suspend = "allow"
155155

156+
ask = "allow"
157+
input = "allow"
158+
notify = "allow"
156159

157160
[metadata]
158161
fields = [

src/rovr/footer/process_container.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -963,13 +963,17 @@ def extract_archive(self, archive_path: str, destination_path: str) -> None:
963963
@work(thread=True)
964964
def paste_items(
965965
self, copied: list[str], has_cut: list[str], dest: str = ""
966-
) -> None:
966+
) -> None | Literal[True]:
967967
"""
968968
Paste copied or cut files to the current directory
969969
Args:
970970
copied (list[str]): A list of items to be copied to the location
971971
has_cut (list[str]): A list of items to be cut to the location
972972
dest (str): The directory to copy to.
973+
974+
Returns:
975+
True: If the operation was successful.
976+
None: If the operation was cancelled or failed.
973977
"""
974978
if dest == "":
975979
dest = getcwd()
@@ -1447,6 +1451,7 @@ def paste_items(
14471451
with suppress(OSError):
14481452
os.rmdir(folder)
14491453
bar.ok()
1454+
return True
14501455

14511456
@work(thread=True)
14521457
def remote_download(self, uris: list[str], paths: list[str]) -> None:

src/rovr/functions/ipc_instances.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def publish_instance(port: int, token: str) -> Path:
4646
os.replace(temporary, destination)
4747
# safety first
4848
destination.chmod(0o600)
49-
except BaseException:
49+
except Exception:
5050
temporary.unlink(missing_ok=True)
5151
raise
5252
return destination
@@ -107,9 +107,11 @@ async def _probe_instance(path: Path, descriptor: Instance) -> InstanceInfo | No
107107
)
108108
except TimeoutError:
109109
return info
110-
except OSError:
110+
except ConnectionRefusedError:
111111
path.unlink(missing_ok=True)
112112
return
113+
except OSError:
114+
return info
113115

114116
try:
115117
writer.write(

src/rovr/functions/ipc_receiver.py

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Any, Callable, Literal, TypedDict, cast
99

1010
from textual import work
11+
from textual.worker import Worker
1112

1213
from rovr.app import Application
1314
from rovr.functions.cwd import getcwd
@@ -149,11 +150,14 @@ async def conn(
149150
# does because we are assuming that ipc paste is
150151
# a prompt (default) else the person kniws what
151152
# they want from rovr
152-
worker = self.app.query_one("ProcessContainer").paste_items(
153-
to_copy, to_cut, getcwd()
154-
)
153+
worker: Worker[None | Literal[True]] = self.app.query_one(
154+
"ProcessContainer"
155+
).paste_items(to_copy, to_cut, getcwd())
155156
await worker.wait()
156-
out = {"copy": len(to_copy), "cut": len(to_cut)}
157+
if ok := bool(worker.result):
158+
out = {"copy": len(to_copy), "cut": len(to_cut)}
159+
else:
160+
err = "user intervened"
157161
case "copy" | "cut":
158162
if not await check_permission(self, action, args):
159163
ok = False
@@ -168,8 +172,12 @@ async def conn(
168172
"keep",
169173
)
170174
if selection not in ("keep", "add", "replace"):
171-
ok = False
172-
err = "selection must be keep, add, or replace"
175+
return assemble_and_write(
176+
writer,
177+
False,
178+
None,
179+
"invalid selection argument, must be one of keep, add, replace",
180+
)
173181
avail = [
174182
p(path)
175183
for path in args[1:]
@@ -181,7 +189,8 @@ async def conn(
181189
if not (path in avail or path.startswith("--"))
182190
]
183191
func: Callable[
184-
[list[str], Literal["reselect", "select", "no"]], None
192+
[list[str], Literal["reselect", "select", "no"]],
193+
Worker[None],
185194
] = (
186195
self.Clipboard.copy_to_clipboard
187196
if args[0] == "copy"
@@ -192,7 +201,8 @@ async def conn(
192201
select = "select"
193202
elif selection == "replace":
194203
select = "reselect"
195-
func(avail, select)
204+
worker = func(avail, select)
205+
await worker.wait()
196206
case _:
197207
ok = False
198208
err = "clipboard action is not valid"
@@ -344,6 +354,16 @@ async def conn(
344354
ok = False
345355
err = "invalid notification arguments"
346356
else:
357+
if notification.markup:
358+
from textual.content import Content
359+
360+
try:
361+
Content.from_markup(notification.message)
362+
except Exception as exc:
363+
return assemble_and_write(
364+
writer, False, None, f"invalid markup: {exc}"
365+
)
366+
347367
self.notify(
348368
notification.message,
349369
title=notification.title or "",
@@ -374,23 +394,26 @@ async def conn(
374394
else:
375395
self.action_suspend_process()
376396
case "ask":
377-
if not await check_permission(self, action, args):
378-
ok = False
379-
err = "denied"
380-
elif len(args) != 1:
397+
if len(args) != 1:
381398
ok = False
382399
err = "too many arguments" if args else "question not provided"
400+
elif not await check_permission(self, action, args):
401+
ok = False
402+
err = "denied"
383403
else:
384404
from rovr.screens import YesOrNo
385405

386406
out: bool = await self.push_screen_wait(YesOrNo(args[0]))
387407
case "input":
388-
if not await check_permission(self, action, args):
408+
if len(args) > 2 or len(args) == 0:
389409
ok = False
390-
err = "denied"
391-
elif len(args) != 1:
410+
err = "too many arguments" if len(args) > 2 else "prompt not provided"
411+
elif len(args) == 1 and args[0] == "--is-path":
392412
ok = False
393-
err = "too many arguments" if args else "prompt not provided"
413+
err = "prompt not provided"
414+
elif not await check_permission(self, action, args):
415+
ok = False
416+
err = "denied"
394417
else:
395418
from rovr.screens import ModalInput
396419

0 commit comments

Comments
 (0)