v0.23.2 bfd5eb4

normalize.go RawHistoryFolder

1// Package url provides URL manipulation utilities for the browser.
2package url
3
4import (
5	"fmt"
6	"net/url"
7	"os"
8	"path/filepath"
9	"strings"
10)
11
12// expandHome expands ~ prefix to user's home directory.
13func expandHome(path string) string {
14	if strings.HasPrefix(path, "~/") {
15		if home, err := os.UserHomeDir(); err == nil {
16			return filepath.Join(home, path[2:])
17		}
18	}
19	return path
20}
21
22// looksLikeFilePath returns true if the input looks like a filesystem path.
23// This includes absolute paths (/path), relative paths (./path, ../path),
24// and home-relative paths (~/path).
25func looksLikeFilePath(input string) bool {
26	switch {
27	case strings.HasPrefix(input, "/"):
28		return true
29	case strings.HasPrefix(input, "./"):
30		return true
31	case strings.HasPrefix(input, "../"):
32		return true
33	case strings.HasPrefix(input, "~/"):
34		return true
35	}
36	return false
37}
38
39// Normalize adds https:// prefix if missing for URL-like inputs.
40// Returns the input unchanged if it already has a scheme or doesn't look like a URL.
41// If the input is an existing local file, returns a file:// URL.
42func Normalize(input string) string {
43	if input == "" {
44		return ""
45	}
46
47	// Already has scheme
48	switch {
49	case strings.HasPrefix(input, "http://"):
50		return input
51	case strings.HasPrefix(input, "https://"):
52		return input
53	case strings.HasPrefix(input, "dumb://"):
54		return input
55	case strings.HasPrefix(input, "file://"):
56		return input
57	case strings.HasPrefix(input, "about:"):
58		return input
59	}
60
61	// Check if input looks like a file path
62	if looksLikeFilePath(input) {
63		expanded := expandHome(input)
64		absPath, err := filepath.Abs(expanded)
65		if err == nil {
66			if _, statErr := os.Stat(absPath); statErr == nil {
67				// File exists, convert to file:// URL
68				return "file://" + absPath
69			}
70		}
71		// Path-like input but file doesn't exist - return unchanged
72		// Don't treat as URL to avoid malformed URLs like https:///path
73		return input
74	}
75
76	// Check if input is an existing file in current directory (relative without ./ prefix)
77	absPath, err := filepath.Abs(input)
78	if err == nil {
79		if _, statErr := os.Stat(absPath); statErr == nil {
80			return "file://" + absPath
81		}
82	}
83
84	// Looks like a URL (contains . and no spaces)
85	if strings.Contains(input, ".") && !strings.Contains(input, " ") {
86		return "https://" + input
87	}
88
89	return input
90}
91
92// LooksLikeURL checks if the input appears to be a URL (not a search query).
93// Returns true for strings like "github.com", "google.com/search", etc.
94// Also returns true for URLs with explicit schemes like "dumb://".
95func LooksLikeURL(input string) bool {
96	if input == "" {
97		return false
98	}
99
100	// Explicit schemes should always be treated as URLs.
101	switch {
102	case strings.HasPrefix(input, "http://"):
103		return true
104	case strings.HasPrefix(input, "https://"):
105		return true
106	case strings.HasPrefix(input, "dumb://"):
107		return true
108	case strings.HasPrefix(input, "file://"):
109		return true
110	case strings.HasPrefix(input, "about:"):
111		return true
112	}
113
114	// Contains a dot and no spaces = likely a URL
115	return strings.Contains(input, ".") && !strings.Contains(input, " ")
116}
117
118// ExtractDomain extracts the normalized domain (host) from a URL string.
119// Normalizes by stripping "www." prefix so youtube.com and www.youtube.com
120// resolve to the same value.
121func ExtractDomain(rawURL string) string {
122	if rawURL == "" {
123		return ""
124	}
125	parsed, err := url.Parse(rawURL)
126	if err != nil || parsed.Host == "" {
127		return ""
128	}
129	return strings.TrimPrefix(parsed.Host, "www.")
130}
131
132// SanitizeDomainForFilename converts a domain to a safe filename with .ico extension.
133// Replaces unsafe filesystem characters with underscores.
134func SanitizeDomainForFilename(domain string) string {
135	return sanitizeDomain(domain) + ".ico"
136}
137
138// SanitizeDomainForPNG converts a domain to a safe filename with .png extension.
139// Used for favicon export for tools like rofi/fuzzel that require PNG format.
140func SanitizeDomainForPNG(domain string) string {
141	return sanitizeDomain(domain) + ".png"
142}
143
144// sanitizeDomain replaces unsafe filesystem characters with underscores.
145func sanitizeDomain(domain string) string {
146	replacer := strings.NewReplacer(
147		":", "_",
148		"/", "_",
149		"\\", "_",
150		"*", "_",
151		"?", "_",
152		"\"", "_",
153		"<", "_",
154		">", "_",
155		"|", "_",
156	)
157	return replacer.Replace(domain)
158}
159
160// SanitizeDomainForPNGSized converts a domain to a safe filename with size suffix.
161// Example: "google.com" with size 32 -> "google.com.32.png"
162// Used for normalized favicon export for tools like rofi/fuzzel.
163func SanitizeDomainForPNGSized(domain string, size int) string {
164	return fmt.Sprintf("%s.%d.png", sanitizeDomain(domain), size)
165}

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