Skip to content

Commit dac83e5

Browse files
committed
Add modal for pasting in links
1 parent 7fc958c commit dac83e5

4 files changed

Lines changed: 143 additions & 9 deletions

File tree

lazy_github/lib/bindings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ class LazyGithubBindings:
168168
# Reaction Bindings
169169
ADD_NEW_REACTION = Binding("R", "add_reaction", "Add reaction", id="react.add")
170170

171+
# Paste link modal
172+
OPEN_PASTE_LINK_MODAL = Binding("ctrl+v", "open_link_paste_modal", "Paste link", id="link.paste")
173+
171174
# Focusing different UI elements
172175
FOCUS_REPOSITORY_TABLE = Binding(
173176
"1",

lazy_github/ui/screens/auth.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from functools import partial
21

32
from textual import work
43
from textual.app import ComposeResult
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import re
2+
3+
from textual import on
4+
from textual.app import ComposeResult
5+
from textual.containers import Container
6+
from textual.content import Content
7+
from textual.screen import ModalScreen
8+
from textual.validation import ValidationResult, Validator
9+
from textual.widgets import Button, Input, Label, Markdown, Rule
10+
11+
from lazy_github.lib.bindings import LazyGithubBindings
12+
from lazy_github.lib.github.backends.protocol import GithubApiRequestFailed
13+
from lazy_github.lib.github.issues import get_issue_by_number
14+
from lazy_github.lib.github.pull_requests import get_full_pull_request
15+
from lazy_github.lib.github.repositories import get_repository_by_name
16+
from lazy_github.models.github import FullPullRequest, Issue, Repository
17+
from lazy_github.ui.widgets.common import LazyGithubFooter, ModalDialogButtons
18+
19+
REPO_PATTERN_STRING = r"(https://)?[^\/]+\/(?P<owner>[^\/]+)\/(?P<repo>[^\/]+)"
20+
REPO_PATTERN = re.compile(REPO_PATTERN_STRING)
21+
ISSUE_PATTERN = re.compile(REPO_PATTERN_STRING + r"\/issues\/(?P<issue>\d+).*")
22+
PR_PATTERN = re.compile(REPO_PATTERN_STRING + r"\/pull\/(?P<pull_request>\d+).*")
23+
24+
PastedLinkSubject = Repository | FullPullRequest | Issue
25+
26+
27+
class GithubLinkValidator(Validator):
28+
def validate(self, value: str) -> ValidationResult:
29+
if REPO_PATTERN.match(value) or ISSUE_PATTERN.match(value) or PR_PATTERN.match(value):
30+
return self.success()
31+
else:
32+
return self.failure()
33+
34+
35+
class PasteLinkContainer(Container):
36+
DEFAULT_CSS = """
37+
PasteLinkContainer {
38+
align: center middle;
39+
}
40+
"""
41+
42+
def compose(self) -> ComposeResult:
43+
yield Markdown("# Paste a web GitHub link:")
44+
yield Label(Content.from_markup("[bold]Link:[/bold]"))
45+
yield Input(id="link_to_paste", placeholder="https://github.com/...", validators=GithubLinkValidator())
46+
yield Rule()
47+
yield ModalDialogButtons(submit_text="Open")
48+
49+
50+
class PasteLinkModal(ModalScreen[PastedLinkSubject]):
51+
DEFAULT_CSS = """
52+
PasteLinkModal {
53+
align: center middle;
54+
content-align: center middle;
55+
}
56+
57+
PasteLinkContainer {
58+
width: 80;
59+
max-height: 20;
60+
border: thick $background 80%;
61+
background: $surface-lighten-3;
62+
}
63+
"""
64+
65+
BINDINGS = [LazyGithubBindings.SUBMIT_DIALOG, LazyGithubBindings.CLOSE_DIALOG]
66+
67+
def compose(self) -> ComposeResult:
68+
yield PasteLinkContainer()
69+
yield LazyGithubFooter()
70+
71+
async def _parse_link_and_action(self, link: str) -> None:
72+
match = PR_PATTERN.match(link) or ISSUE_PATTERN.match(link) or REPO_PATTERN.match(link)
73+
if match is None:
74+
return
75+
self.query_one("#submit", Button).disabled = True
76+
self.query_one("#cancel", Button).disabled = True
77+
78+
groups = match.groupdict()
79+
80+
repo = await get_repository_by_name(f"{groups['owner']}/{groups['repo']}")
81+
if not repo:
82+
self.notify("Could not find repository!", title="Unknown repo", severity="error")
83+
return
84+
85+
if issue_number := groups.get("issue"):
86+
try:
87+
issue = await get_issue_by_number(repo, int(issue_number))
88+
self.dismiss(issue)
89+
except GithubApiRequestFailed:
90+
self.notify("Could not find issue!", title="Unknown issue", severity="error")
91+
elif pr_number := groups.get("pull_request"):
92+
try:
93+
pr = await get_full_pull_request(repo, int(pr_number))
94+
self.dismiss(pr)
95+
except GithubApiRequestFailed:
96+
self.notify("Could not find pull request!", title="Unknown PR", severity="error")
97+
else:
98+
self.notify("Unknown link")
99+
self.query_one("#submit", Button).disabled = False
100+
self.query_one("#cancel", Button).disabled = False
101+
102+
@on(Button.Pressed, "#submit")
103+
async def action_submit(self) -> None:
104+
link = self.query_one("#link_to_paste", Input)
105+
await self._parse_link_and_action(link.value)
106+
107+
@on(Button.Pressed, "#cancel")
108+
async def action_close(self) -> None:
109+
self.dismiss(None)

lazy_github/ui/screens/primary.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@
3333
ReviewsAndCommentsLoaded,
3434
WorkflowRunSelected,
3535
)
36-
from lazy_github.models.github import Issue, PartialPullRequest, Repository, WorkflowRun
36+
from lazy_github.models.github import FullPullRequest, Issue, PartialPullRequest, Repository, WorkflowRun
3737
from lazy_github.ui.screens.create_or_edit_pull_request import CreateOrEditPullRequestModal
3838
from lazy_github.ui.screens.debug import DebugModal
39+
from lazy_github.ui.screens.link_paste import PasteLinkModal
3940
from lazy_github.ui.screens.new_issue import NewIssueModal
4041
from lazy_github.ui.screens.notifications import NotificationsModal
4142
from lazy_github.ui.screens.settings import SettingsModal
@@ -300,12 +301,15 @@ async def load_repository(self, repo: Repository) -> None:
300301
await self.selections.load_repository(repo)
301302

302303
async def load_pull_request(self, pull_request: PartialPullRequest, focus_pr_details: bool = True) -> None:
303-
try:
304-
full_pr = await get_full_pull_request(pull_request.repo, pull_request.number)
305-
except GithubApiRequestFailed:
306-
lg.error("API error loading PR!")
307-
self.notify("Error fetching pull request!", severity="error", title="API Error")
308-
return
304+
if isinstance(pull_request, FullPullRequest):
305+
full_pr = pull_request
306+
else:
307+
try:
308+
full_pr = await get_full_pull_request(pull_request.repo, pull_request.number)
309+
except GithubApiRequestFailed:
310+
lg.error("API error loading PR!")
311+
self.notify("Error fetching pull request!", severity="error", title="API Error")
312+
return
309313

310314
tabbed_content = self.query_one("#selection_detail_tabs", TabbedContent)
311315
await tabbed_content.clear_panes()
@@ -414,7 +418,7 @@ async def search(self, query: str) -> Hits:
414418

415419

416420
class LazyGithubMainScreen(Screen):
417-
BINDINGS = [LazyGithubBindings.OPEN_NOTIFICATIONS_MODAL]
421+
BINDINGS = [LazyGithubBindings.OPEN_NOTIFICATIONS_MODAL, LazyGithubBindings.OPEN_PASTE_LINK_MODAL]
418422
COMMANDS = {MainScreenCommandProvider}
419423
notification_refresh_timer: Timer | None = None
420424

@@ -432,6 +436,25 @@ async def set_repository(self, repo: Repository) -> None:
432436
self.set_currently_loaded_repo(repo)
433437
await self.main_view_pane.load_repository(repo)
434438

439+
@work
440+
async def action_open_link_paste_modal(self) -> None:
441+
link_subject = await self.app.push_screen_wait(PasteLinkModal())
442+
match link_subject:
443+
case FullPullRequest():
444+
await self.set_repository(link_subject.repo)
445+
await self.main_view_pane.load_pull_request(link_subject)
446+
self.notify("Opening pull request for link")
447+
return
448+
case Issue():
449+
await self.set_repository(link_subject.repo)
450+
await self.main_view_pane.load_issue(link_subject)
451+
self.notify("Opening issue for link")
452+
return
453+
case Repository():
454+
await self.set_repository(link_subject)
455+
self.notify("Opening repository for link")
456+
return
457+
435458
@work
436459
async def action_view_notifications(self) -> None:
437460
notification = await self.app.push_screen_wait(NotificationsModal())

0 commit comments

Comments
 (0)