Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import (
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
)

// Stdout is the io.Writer to which executed commands write standard output.
Expand All @@ -19,12 +21,34 @@ var Stdout io.Writer = os.Stdout
var Stderr io.Writer = os.Stderr

// OpenFile opens new browser window for the file path.
// A URL fragment after '#' (for example "index.html#section") is preserved.
func OpenFile(path string) error {
path, err := filepath.Abs(path)
path, frag := splitFragment(path)
abs, err := filepath.Abs(path)
if err != nil {
return err
}
return OpenURL("file://" + path)
return OpenURL(fileURL(abs, frag))
}

// splitFragment separates a filesystem path from an optional URL fragment.
func splitFragment(path string) (file, fragment string) {
i := strings.LastIndex(path, "#")
if i < 0 {
return path, ""
}
return path[:i], path[i+1:]
}

// fileURL builds a file: URL for an absolute filesystem path, with optional fragment.
func fileURL(absPath, fragment string) string {
p := filepath.ToSlash(absPath)
if !strings.HasPrefix(p, "/") {
// Windows drive path, e.g. C:/foo → /C:/foo
p = "/" + p
}
u := url.URL{Scheme: "file", Path: p, Fragment: fragment}
return u.String()
}

// OpenReader consumes the contents of r and presents the
Expand Down
32 changes: 32 additions & 0 deletions browser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package browser

import (
"strings"
"testing"
)

func TestSplitFragment(t *testing.T) {
file, frag := splitFragment(`c:\some-file.html#basic`)
if file != `c:\some-file.html` {
t.Fatalf("file = %q", file)
}
if frag != "basic" {
t.Fatalf("frag = %q", frag)
}
f2, g2 := splitFragment("index.html")
if f2 != "index.html" || g2 != "" {
t.Fatalf("got %q %q", f2, g2)
}
}

func TestFileURLPreservesFragment(t *testing.T) {
got := fileURL("/tmp/doc.html", "basic")
want := "file:///tmp/doc.html#basic"
if got != want {
t.Fatalf("got %q want %q", got, want)
}
got = fileURL("C:/some-file.html", "basic")
if !strings.HasPrefix(got, "file:///C:/some-file.html") || !strings.HasSuffix(got, "#basic") {
t.Fatalf("windows-style got %q", got)
}
}
5 changes: 2 additions & 3 deletions browser_windows.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package browser

import "golang.org/x/sys/windows"

// Windows ShellExecute drops fragments on file: URLs. FileProtocolHandler keeps them.
func openBrowser(url string) error {
return windows.ShellExecute(0, nil, windows.StringToUTF16Ptr(url), nil, nil, windows.SW_SHOWNORMAL)
return runCmd("rundll32", "url.dll,FileProtocolHandler", url)
}