diff --git a/.editorconfig b/.editorconfig index f3cbbbf..e22780f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,7 +13,7 @@ tab_width = 4 indent_size = 4 [*.md] -indent_size = 4 +indent_size = 2 trim_trailing_whitespace = false eclint_indent_style = unset diff --git a/README.md b/README.md index 297a0d5..c46dc23 100644 --- a/README.md +++ b/README.md @@ -12,30 +12,40 @@ There are some bugs on Windows because I don't use Windows. Feel free to open PR ## Features -- Substitute environment variables and ~ -- Tab completion -- Configurable and interactive prompt -- Command history -- Command aliases -- Context prompt (e.g. git branch) -- Context aware completions +- Substitute environment variables and ~ +- Tab completion +- Configurable and interactive prompt +- Command history +- Command aliases +- Context prompt (e.g. git branch) +- Context aware completions + +### Builtins + +- `calc` (simple calculator) +- `cd` (change directory) +- `echo` (print arguments to stdout) +- `exit` (exit shell session) +- `printf` (print formatted string to stdout) +- `time` (time command execution) +- `zu` (similar to [ajeetdsouza/zoxide](https://github.com/ajeetdsouza/zoxide), change directory based on history and frequency) ## Upcoming -- More configuration options -- More builtins +- More configuration options +- More builtins ## Configuration `~/.config/smash/config.toml` -- `ps1`: Prompt shown on user input -- `ps2`: Prompt shown on the executed commands -- `alias`: Map of command aliases where keys can be a string or list of strings -- `color`: Map of colors supporting all [charmbracelet/lipgloss](https://github.com/charmbracelet/lipgloss) colors - - `completion_text`: text color of completion list - - `completion_selected_bg`: background color of selected completion entry -- `on_start`: List of commands to run on shell start +- `ps1`: Prompt shown on user input +- `ps2`: Prompt shown on the executed commands +- `alias`: Map of command aliases where keys can be a string or list of strings +- `color`: Map of colors supporting all [charmbracelet/lipgloss](https://github.com/charmbracelet/lipgloss) colors + - `completion_text`: text color of completion list + - `completion_selected_bg`: background color of selected completion entry +- `on_start`: List of commands to run on shell start ## Install diff --git a/cmd/proto/proto.go b/cmd/proto/proto.go new file mode 100644 index 0000000..bf4a235 --- /dev/null +++ b/cmd/proto/proto.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" +) + +func main() { + // find all protobuf files + filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if filepath.Ext(path) == ".proto" { + fmt.Fprintf(os.Stderr, "compiling %s\n", path) + // compile the protobuf file + if err := run("protoc", "--go_out=.", path); err != nil { + return err + } + } + return nil + }) +} + +func run(name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} diff --git a/go.mod b/go.mod index 1aeee67..a49d52d 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/charmbracelet/bubbletea v1.1.1 github.com/charmbracelet/lipgloss v0.13.0 github.com/tsukinoko-kun/calc v1.1.2 + google.golang.org/protobuf v1.34.2 ) require ( diff --git a/go.sum b/go.sum index 225b2d7..70dedf2 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4h github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -45,3 +47,7 @@ golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= diff --git a/internal/assert/assert.go b/internal/assert/assert.go index 48b9a14..d8ab6e7 100644 --- a/internal/assert/assert.go +++ b/internal/assert/assert.go @@ -28,3 +28,9 @@ func SmallerThan[T cmp.Ordered](x, smallerThan T, errorMessage string) { panic(fmt.Sprintf("assertion failed: SmallerThan(%v, %v): %s", x, smallerThan, errorMessage)) } } + +func NotNil[T any](x *T, errorMessage string) { + if x == nil { + panic(fmt.Sprintf("assertion failed: NotNil(%v): %s", x, errorMessage)) + } +} diff --git a/internal/env/config.go b/internal/env/config.go index 2065155..1e2177d 100644 --- a/internal/env/config.go +++ b/internal/env/config.go @@ -13,11 +13,13 @@ var ( Config *smashConfig Alias map[string][]string HistoryFile string + ZuFile string ) func init() { loadVars() HistoryFile = filepath.Join(getConfigDir(), "history.txt") + ZuFile = filepath.Join(getConfigDir(), "zu.bin") Config = getConfigFile() if Config.Alias != nil { Alias = make(map[string][]string, len(Config.Alias)) diff --git a/internal/shell/history/history.go b/internal/shell/history/history.go index 1eada9b..666293e 100644 --- a/internal/shell/history/history.go +++ b/internal/shell/history/history.go @@ -7,7 +7,10 @@ import ( "smash/internal/env" ) -const maxHistory = 512 +const ( + historyHwm = 128 + historyLwm = 64 +) var ( history []string @@ -24,9 +27,10 @@ func loadHistory() error { scanner := bufio.NewScanner(file) for scanner.Scan() { history = append(history, scanner.Text()) - if len(history) > maxHistory { - history = history[1:] - } + } + + if len(history) > historyHwm { + history = history[historyLwm:] } currentIndex = len(history) @@ -66,12 +70,14 @@ func HistoryForward() (string, bool) { // AddToHistory adds a new entry to the history func AddToHistory(entry string) error { + // ensure the latest history is loaded if len(history) == 0 { if err := loadHistory(); err != nil { return err } } + // check if the latest history entry is the same as the new entry if len(history) != 0 { latestHistoryEntry := history[len(history)-1] if latestHistoryEntry == entry { @@ -79,22 +85,38 @@ func AddToHistory(entry string) error { } } + // append new entry to history in memory history = append(history, entry) - if len(history) > maxHistory { - history = history[1:] - } - currentIndex = len(history) - // Append to file - file, err := os.OpenFile(env.HistoryFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) - if err != nil { - return fmt.Errorf("failed to open history file: %w", err) - } - defer file.Close() + // ensure history length does not exceed the high water mark + if len(history) > historyHwm { + history = history[historyLwm:] - if _, err := fmt.Fprintln(file, entry); err != nil { - return fmt.Errorf("failed to write to history file: %w", err) + // write whole history to history file + file, err := os.Create(env.HistoryFile) + if err != nil { + return fmt.Errorf("failed to open history file: %w", err) + } + defer file.Close() + writer := bufio.NewWriter(file) + defer writer.Flush() + for _, entry := range history { + if _, err := writer.WriteString(entry + "\n"); err != nil { + return fmt.Errorf("failed to write to history file: %w", err) + } + } + } else { + // append new entry to history file + file, err := os.OpenFile(env.HistoryFile, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("failed to open history file: %w", err) + } + defer file.Close() + if _, err := file.WriteString(entry + "\n"); err != nil { + return fmt.Errorf("failed to write to history file: %w", err) + } } + currentIndex = len(history) return nil } diff --git a/internal/shell/parser/exe.go b/internal/shell/parser/exe.go index 184cb1f..4fdbfb5 100644 --- a/internal/shell/parser/exe.go +++ b/internal/shell/parser/exe.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "smash/internal/env" + "smash/internal/shell/parser/zu" "smash/internal/system" "strconv" "strings" @@ -250,6 +251,30 @@ func (e *exe) cd() error { } } +func (e *exe) z() error { + switch len(e.Args) { + case 0: + return pushDir(env.GetUser().HomeDir) + case 1: + if e.Args[0] == "-c" { + return zu.Clear() + } else if e.Args[0] == "-l" { + if l, err := zu.List(); err != nil { + return err + } else { + for _, e := range l { + _, _ = fmt.Println(e) + } + return nil + } + } else { + return zu.To(e.Args[0]) + } + default: + return errors.New("z: too many operands") + } +} + func (e *exe) smashfetch(stdout io.Writer) error { _, _ = fmt.Fprintln(stdout, system.SmashFetch()) return nil diff --git a/internal/shell/parser/shell_unix.go b/internal/shell/parser/shell_unix.go index eab4523..5a15b9b 100644 --- a/internal/shell/parser/shell_unix.go +++ b/internal/shell/parser/shell_unix.go @@ -11,6 +11,7 @@ var InternalToolNames = [...]string{ "time", "calc", "cd", + "zu", } func (e *exe) internal(stdin io.Reader, stdout io.Writer, stderr io.Writer) (bool, error) { @@ -27,6 +28,8 @@ func (e *exe) internal(stdin io.Reader, stdout io.Writer, stderr io.Writer) (boo return true, e.calc(stdin, stdout) case "cd": return true, e.cd() + case "zu": + return true, e.z() case "smashfetch": return true, e.smashfetch(stdout) case "sleep": diff --git a/internal/shell/parser/shell_windows.go b/internal/shell/parser/shell_windows.go index 4afb84c..76cb426 100644 --- a/internal/shell/parser/shell_windows.go +++ b/internal/shell/parser/shell_windows.go @@ -16,6 +16,7 @@ var InternalToolNames = [...]string{ "time", "calc", "cd", + "zu", "env", } @@ -33,6 +34,8 @@ func (e *exe) internal(stdin io.Reader, stdout io.Writer, stderr io.Writer) (boo return true, e.calc(stdin, stdout) case "cd": return true, e.cd() + case "zu": + return true, e.z() case "smashfetch": return true, e.smashfetch(stdout) case "sleep": diff --git a/internal/shell/parser/zu/zu.go b/internal/shell/parser/zu/zu.go new file mode 100644 index 0000000..315d02f --- /dev/null +++ b/internal/shell/parser/zu/zu.go @@ -0,0 +1,199 @@ +package zu + +import ( + "errors" + "fmt" + "io" + "math" + "os" + "path/filepath" + "smash/internal/assert" + "smash/internal/env" + "sort" + "strings" + "sync" + + "google.golang.org/protobuf/proto" +) + +var ( + mut = sync.RWMutex{} +) + +func getZu() (*Zu, error) { + mut.Lock() + defer mut.Unlock() + file, err := os.OpenFile(env.ZuFile, os.O_RDONLY|os.O_CREATE, 0644) + if err != nil { + return nil, errors.Join(errors.New("failed to open zu file"), err) + } + defer file.Close() + + // read the file + z := &Zu{} + b, err := io.ReadAll(file) + if err != nil { + return nil, errors.Join(errors.New("failed to read zu file"), err) + } + if err := proto.Unmarshal(b, z); err != nil { + return nil, errors.Join(errors.New("failed to parse zu file"), err) + } + return z, nil +} + +func saveZu(z *Zu) error { + z.Sort() + + mut.RLock() + defer mut.RUnlock() + file, err := os.Create(env.ZuFile) + if err != nil { + return errors.Join(errors.New("failed to create zu file"), err) + } + defer file.Close() + + // write the file + b, err := proto.Marshal(z) + if err != nil { + return errors.Join(errors.New("failed to marshal zu file"), err) + } + if _, err := file.Write(b); err != nil { + return errors.Join(errors.New("failed to write zu file"), err) + } + return nil +} + +func (z *Zu) FindOrCreateExact(path string) *ZuEntry { + mut.Lock() + defer mut.Unlock() + + for _, e := range z.Entries { + if e.Path == path { + return e + } + } + e := &ZuEntry{Path: path, AccessCount: 0} + z.Entries = append(z.Entries, e) + return e +} + +func exists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +func (z *Zu) delete(path string) { + mut.Lock() + defer mut.Unlock() + for i, e := range z.Entries { + if e.Path == path { + z.Entries = append(z.Entries[:i], z.Entries[i+1:]...) + break + } + } +} + +func (z *Zu) FindMostCommonMatch(target string) *ZuEntry { + mut.RLock() + defer mut.RUnlock() + + var best *ZuEntry + for _, e := range z.Entries { + if filepath.Base(e.Path) == target { + if !exists(e.Path) { + // remove non-existing Entries + + continue + } + best = e + } + } + if best != nil { + return best + } + // no exact match found, try partial match + for _, e := range z.Entries { + if strings.Contains(strings.ToLower(filepath.Base(e.Path)), strings.ToLower(target)) { + best = e + } + } + return best +} + +// Sorts the entries by access count descending +func (z *Zu) Sort() { + mut.Lock() + defer mut.Unlock() + sort.Slice(z.Entries, func(i, j int) bool { + return z.Entries[i].AccessCount > z.Entries[j].AccessCount + }) + if len(z.Entries) > 64 { + // cut off the least used Entries + z.Entries = z.Entries[:32] + } +} + +// Clear clears the zu +func Clear() error { + mut.Lock() + z := &Zu{} + mut.Unlock() + return saveZu(z) +} + +func List() ([]string, error) { + mut.RLock() + defer mut.RUnlock() + z, err := getZu() + if err != nil { + return nil, errors.Join(errors.New("failed to get zu"), err) + } + assert.NotNil(z, "*Zu is nil") + + var paths []string + for _, e := range z.Entries { + paths = append(paths, fmt.Sprintf("%s (%d)", e.Path, e.AccessCount)) + } + return paths, nil +} + +func To(target string) error { + // get the current zu + z, err := getZu() + if err != nil { + return errors.Join(errors.New("failed to get zu"), err) + } + assert.NotNil(z, "*Zu is nil") + + // look if the target exists + stat, err := os.Stat(target) + if err == nil { + if stat.IsDir() { + // add the target to the zu + abs, err := filepath.Abs(target) + if err != nil { + return errors.Join(errors.New("failed to get absolute path"), err) + } + e := z.FindOrCreateExact(abs) + if e.AccessCount < math.MaxInt64 { + e.AccessCount++ + } + _ = saveZu(z) + // change to the target directory + return os.Chdir(abs) + } else { + return errors.New("target exists but is not a directory") + } + } + + // find matching entry in zu + e := z.FindMostCommonMatch(target) + if e == nil { + return errors.New("no matching entry found") + } + + // change to the target directory + e.AccessCount++ + _ = saveZu(z) + return os.Chdir(e.Path) +} diff --git a/internal/shell/parser/zu/zu.pb.go b/internal/shell/parser/zu/zu.pb.go new file mode 100644 index 0000000..6099504 --- /dev/null +++ b/internal/shell/parser/zu/zu.pb.go @@ -0,0 +1,217 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.28.2 +// source: internal/shell/parser/zu/zu.proto + +package zu + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Zu struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entries []*ZuEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` +} + +func (x *Zu) Reset() { + *x = Zu{} + if protoimpl.UnsafeEnabled { + mi := &file_internal_shell_parser_zu_zu_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Zu) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Zu) ProtoMessage() {} + +func (x *Zu) ProtoReflect() protoreflect.Message { + mi := &file_internal_shell_parser_zu_zu_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Zu.ProtoReflect.Descriptor instead. +func (*Zu) Descriptor() ([]byte, []int) { + return file_internal_shell_parser_zu_zu_proto_rawDescGZIP(), []int{0} +} + +func (x *Zu) GetEntries() []*ZuEntry { + if x != nil { + return x.Entries + } + return nil +} + +type ZuEntry struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + AccessCount int64 `protobuf:"varint,2,opt,name=accessCount,proto3" json:"accessCount,omitempty"` +} + +func (x *ZuEntry) Reset() { + *x = ZuEntry{} + if protoimpl.UnsafeEnabled { + mi := &file_internal_shell_parser_zu_zu_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ZuEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ZuEntry) ProtoMessage() {} + +func (x *ZuEntry) ProtoReflect() protoreflect.Message { + mi := &file_internal_shell_parser_zu_zu_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ZuEntry.ProtoReflect.Descriptor instead. +func (*ZuEntry) Descriptor() ([]byte, []int) { + return file_internal_shell_parser_zu_zu_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *ZuEntry) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *ZuEntry) GetAccessCount() int64 { + if x != nil { + return x.AccessCount + } + return 0 +} + +var File_internal_shell_parser_zu_zu_proto protoreflect.FileDescriptor + +var file_internal_shell_parser_zu_zu_proto_rawDesc = []byte{ + 0x0a, 0x21, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x68, 0x65, 0x6c, 0x6c, + 0x2f, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x2f, 0x7a, 0x75, 0x2f, 0x7a, 0x75, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x7a, 0x75, 0x22, 0x6b, 0x0a, 0x02, 0x7a, 0x75, 0x12, 0x26, 0x0a, + 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, + 0x2e, 0x7a, 0x75, 0x2e, 0x7a, 0x75, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, + 0x74, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x3d, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x12, + 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, + 0x74, 0x68, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x1a, 0x5a, 0x18, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2f, 0x73, 0x68, 0x65, 0x6c, 0x6c, 0x2f, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x2f, 0x7a, 0x75, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_internal_shell_parser_zu_zu_proto_rawDescOnce sync.Once + file_internal_shell_parser_zu_zu_proto_rawDescData = file_internal_shell_parser_zu_zu_proto_rawDesc +) + +func file_internal_shell_parser_zu_zu_proto_rawDescGZIP() []byte { + file_internal_shell_parser_zu_zu_proto_rawDescOnce.Do(func() { + file_internal_shell_parser_zu_zu_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_shell_parser_zu_zu_proto_rawDescData) + }) + return file_internal_shell_parser_zu_zu_proto_rawDescData +} + +var file_internal_shell_parser_zu_zu_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_internal_shell_parser_zu_zu_proto_goTypes = []any{ + (*Zu)(nil), // 0: zu.zu + (*ZuEntry)(nil), // 1: zu.zu.entry +} +var file_internal_shell_parser_zu_zu_proto_depIdxs = []int32{ + 1, // 0: zu.zu.entries:type_name -> zu.zu.entry + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_internal_shell_parser_zu_zu_proto_init() } +func file_internal_shell_parser_zu_zu_proto_init() { + if File_internal_shell_parser_zu_zu_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_internal_shell_parser_zu_zu_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*Zu); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_internal_shell_parser_zu_zu_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*ZuEntry); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_internal_shell_parser_zu_zu_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_internal_shell_parser_zu_zu_proto_goTypes, + DependencyIndexes: file_internal_shell_parser_zu_zu_proto_depIdxs, + MessageInfos: file_internal_shell_parser_zu_zu_proto_msgTypes, + }.Build() + File_internal_shell_parser_zu_zu_proto = out.File + file_internal_shell_parser_zu_zu_proto_rawDesc = nil + file_internal_shell_parser_zu_zu_proto_goTypes = nil + file_internal_shell_parser_zu_zu_proto_depIdxs = nil +} diff --git a/internal/shell/parser/zu/zu.proto b/internal/shell/parser/zu/zu.proto new file mode 100644 index 0000000..963a283 --- /dev/null +++ b/internal/shell/parser/zu/zu.proto @@ -0,0 +1,13 @@ +syntax="proto3"; +package zu; + +option go_package = "internal/shell/parser/zu"; + +message zu { + message entry { + string path = 1; + int64 accessCount = 2; + } + + repeated entry entries = 1; +} diff --git a/main.go b/main.go index 24f8abd..bb4e672 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ func main() { q.Add("title", "runtime error") q.Add("body", fmt.Sprintf("Runtime error caught in main: %s\n\nExplain what you did:\n\nOS: %s \nArch: %s \nStack:\n```\n%s\n```\n", r, runtime.GOOS, runtime.GOARCH, string(debug.Stack()))) issueUrl.RawQuery = q.Encode() - _, _ = fmt.Fprintf(os.Stderr, "smash: runtime error: %s\nplease report this: %s\n", r, issueUrl.String()) + _, _ = fmt.Fprintf(os.Stderr, "smash: runtime error: %s\n\nStack:\n%s\n\nplease report this: %s\n", r, string(debug.Stack()), issueUrl.String()) <-time.After(15 * time.Second) } }() @@ -54,7 +54,9 @@ func main() { if userInput == "" { continue } - _ = history.AddToHistory(userInput) + if err := history.AddToHistory(userInput); err != nil { + panic(err) + } ps2, err := shell.Ps2() if err != nil { fmt.Printf("ps2 error: %v\n", err)