main 5041353

navigation_url_normalizer.go RawHistoryFolder

1package usecase
2
3import (
4	"context"
5	stdurl "net/url"
6	"strings"
7
8	"github.com/bnema/dumber/internal/application/port"
9	domainurl "github.com/bnema/dumber/internal/domain/url"
10)
11
12// NavigationURLNormalizer normalizes user navigation input while keeping
13// filesystem probing behind an application port.
14type NavigationURLNormalizer struct {
15	localPaths port.LocalPathResolver
16}
17
18// NewNavigationURLNormalizer creates a navigation URL normalizer.
19func NewNavigationURLNormalizer(localPaths port.LocalPathResolver) *NavigationURLNormalizer {
20	return &NavigationURLNormalizer{localPaths: localPaths}
21}
22
23// Normalize converts existing local paths to file:// URLs, then falls back to
24// the pure domain URL normalizer for URL-like decisions.
25func (n *NavigationURLNormalizer) Normalize(ctx context.Context, input string) string {
26	if input == "" {
27		return ""
28	}
29	if n != nil && n.localPaths != nil && shouldProbeLocalPath(input) {
30		if absPath, ok, err := n.localPaths.ResolveExistingPath(ctx, input); err == nil && ok {
31			return (&stdurl.URL{Scheme: "file", Path: absPath}).String()
32		}
33	}
34	return domainurl.Normalize(input)
35}
36
37// BuildNavigationURL resolves local paths before applying bang shortcuts and search fallback.
38func (n *NavigationURLNormalizer) BuildNavigationURL(
39	ctx context.Context,
40	input string,
41	shortcutURLs map[string]string,
42	defaultSearch string,
43) string {
44	return BuildNavigationURL(ctx, input, n.normalize, shortcutURLs, defaultSearch)
45}
46
47// BuildNavigationURL resolves a user navigation string using the shared CLI/UI policy.
48func BuildNavigationURL(
49	ctx context.Context,
50	input string,
51	normalize func(context.Context, string) string,
52	shortcutURLs map[string]string,
53	defaultSearch string,
54) string {
55	if _, _, found := domainurl.ParseBangShortcut(input); !found && normalize != nil {
56		normalized := normalize(ctx, input)
57		if normalized != input {
58			return normalized
59		}
60	}
61	return domainurl.BuildSearchURL(input, shortcutURLs, defaultSearch)
62}
63
64func (n *NavigationURLNormalizer) normalize(ctx context.Context, input string) string {
65	if n == nil {
66		return domainurl.Normalize(input)
67	}
68	return n.Normalize(ctx, input)
69}
70
71func shouldProbeLocalPath(input string) bool {
72	if input == "" {
73		return false
74	}
75	if hasPreservedNavigationScheme(input) {
76		return false
77	}
78	return true
79}
80
81func hasPreservedNavigationScheme(input string) bool {
82	parsed, err := stdurl.Parse(input)
83	if err != nil || parsed.Scheme == "" {
84		return false
85	}
86
87	scheme := strings.ToLower(parsed.Scheme)
88	lowerInput := strings.ToLower(input)
89
90	switch scheme {
91	case "http", "https", "dumb", "file", "vscode", "vscode-insiders", "spotify", "steam":
92		return strings.HasPrefix(lowerInput, scheme+"://") && domainurl.Normalize(input) == input
93	case "blob":
94		return strings.HasPrefix(lowerInput, "blob:") && strings.Contains(lowerInput, "://") &&
95			domainurl.Normalize(input) == input
96	case "about", "data", "javascript", "mailto":
97		return strings.HasPrefix(lowerInput, scheme+":") && domainurl.Normalize(input) == input
98	default:
99		return false
100	}
101}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close