Prompts
We've went quite far with our trusty ">" prompt, but now we've done enough that
Prompts
We've went quite far with our trusty ">" prompt, but now we've done enough that we might want to start using this as our login shell.
We'd like customizable prompts. We can start with just using $PROMPT as our
prompt if it exists, and falling back on our trusty old ">". While we're changing
things, we should probably print our prompt to os.Stderr, which is more
appropriate than Stdout for status-y type things like prompts or progress.
"PrintPrompt Implementation"
if p := os.Getenv("PROMPT"); p != "" {
fmt.Fprintf(os.Stderr, "\n%s", p)
} else {
fmt.Fprintf(os.Stderr, "\n> ")
}
And now if we wanted we could type set PROMPT $ to make our shell look like
sh.. except we can't, because the $ gets treated as an environment variable
by our parser before it gets to "set".
We can start by using the standard Go os.ExpandEnv function in our replacement
instead of our naive loop that just replaced any tokens that start with '$' with
the environment variable. This will also have the benefit of making our parser a
little more standard, and allowing us to use environment variables inside of
tokens, such as $GOPATH/bin too.
"Replace environment variables in command"
args := make([]string, 0, len(parsed))
for _, val := range parsed[1:] {
args = append(args, os.ExpandEnv(val))
}
But that doesn't get us very far, either, because we still can't do anything
dynamic. We can try setting a $ environment variable that evaluates to the
string "$" as a way to try escaping "$" in the shell and then something like
"set PROMPT $$PWD>" would theoretically set the prompt variable to the string
"$PWD>", but we'd have to hope that the ExpandEnv interprets $$PWD as ($$)PWD
and not $($PWD). Let's give it a shot anyways, but keeping in mind that if it
works we're depending on undocumented behaviour that may change in a future
version of Go.
"Initialize Terminal" +=
os.Setenv("$", "$")
It seems to work, so let's improve our PrintPrint implementation to dynamically expand variables that were set in the PROMPT variable too
"PrintPrompt Implementation"
if p := os.Getenv("PROMPT"); p != "" {
fmt.Fprintf(os.Stderr, "\n%s", os.ExpandEnv(p))
} else {
fmt.Fprintf(os.Stderr, "\n> ")
}
We'd now be able to do something like set PROMPT '$$PWD>' to get the current
working directory in our prompt, except that our cd implementation doesn't
set PWD. Let's fix it to set both
"Handle cd command"
if len(args) == 0 {
return fmt.Errorf("Must provide an argument to cd")
}
old, _ := os.Getwd()
err := os.Chdir(args[0])
if err == nil {
new, _ := os.Getwd()
os.Setenv("PWD", new)
os.Setenv("OLDPWD", old)
}
return err
There, now we can even do something like set PROMPT '$$PWD:$$?> ' to get the last return code too.
What would be great though, was if we could use a '!' prefix to signify running a command to run in order to get the prompt, similarly to how our tab completion works.
It shouldn't be too hard to add a similar check to run the command with its standard out being directed to stderr if our PROMPT variable starts with a '!'. We could even expand the environment variables in the same way we're already doing.
"PrintPrompt Implementation"
if p := os.Getenv("PROMPT"); p != "" {
if len(p) > 1 && p[0] == '!' {
<<<Run command for prompt>>>
} else {
fmt.Fprintf(os.Stderr, "\n%s", os.ExpandEnv(p))
}
} else {
fmt.Fprintf(os.Stderr, "\n> ")
}
"Run command for prompt"
input := os.ExpandEnv(p[1:])
split := strings.Fields(input)
cmd := exec.Command(split[0], split[1:]...)
cmd.Stdout = os.Stderr
if err := cmd.Run(); err != nil {
// Fall back on our standard prompt, with a warning.
fmt.Fprintf(os.Stderr, "\nInvalid prompt command\n> ")
}
But Run will return an err if the command exits with a non-zero error status,
which may have nothing to do with our prompt. If it fails to run, the error is
of type *ExitError according to the Run() documentation. So let's only print
our warning if the error returned is of that type.
"Run command for prompt"
input := os.ExpandEnv(p[1:])
split := strings.Fields(input)
cmd := exec.Command(split[0], split[1:]...)
cmd.Stdout = os.Stderr
if err := cmd.Run(); err != nil {
if _, ok := err.(*exec.ExitError); !ok {
// Fall back on our standard prompt, with a warning.
fmt.Fprintf(os.Stderr, "\nInvalid prompt command\n> ")
}
}
Now that we're customizing prompts, we might notice that if we set a prompt in
our startup script, the first prompt gets printed before our ~/.goshrc script
is sourced. Let's add it to Initialize Shell
"Initialize Shell" +=
PrintPrompt()
and take it out of Initialize Terminal. (We'll have to do a little refactoring of our blocks that we probably should have done upfront.)
"Initialize Terminal"
// Initialize the terminal
t, err := term.Open("/dev/tty")
if err != nil {
panic(err)
}
// Restore the previous terminal settings at the end of the program
defer t.Restore()
t.SetCbreak()
terminal = t
<<<Create SIGCHLD chan>>>
<<<Ignore certain signal types>>>
os.Setenv("$", "$")
Now, we can create write prompts in any language of our choosing, as long as we can print to standard out in our language.
Related Documents
You must use artifacts for
<citation_instructions>If the assistant's response is based on content returned by the web_search, drive_search, google_drive_search, or google_drive_fetch tool, the assistant must always appropriately cite its response. Here are the rules for good citations:
starProject
- **Description:** Multilingual Document Layout Parsing in a Single Vision-Language Model
Prompts
<img width="395" alt="prompts.chat" src="https://github.com/user-attachments/assets/e0d0e32d-d2ce-4459-9f37-e951d9f4f5de" />
Daily Agent Tasks Framework
**Your Daily System for Working with Claude and Codex**