Closed bw1faeh0 closed 3 years ago
The screen shot has two lines.
If the question is about the second line:
horizontal-scroll-mode
which limits the input line to not wrap, but it still extends to the right edge of the terminal display.If the question is about the first line:
The screenshot is from a z-shell (zsh).\ The question is about the second line.\ In the case the input string becomes too long, the far right of the prompt disapears:
So, at the end, the input length is not limited, the prompt will be overwritten, which is OK in my opinion.
I see, then both of my answers are relevant. 👍
For example:
console.getwidth() - console.cellcount(the_string)
column (see docs for usage details).In other words, this is almost entirely about using ANSI escape codes (the only reason console.cellcount() is needed is to deal with complex Unicode scripts, where some symbols use more than one cell).
Or put another way, if you can find the program or script that was used to generate the zsh prompt string, it will likely work with Clink if the script language is translated to Lua instead of shell script. Because it's not about the script language; it's just a matter of using ANSI escape codes.
But again, Readline won't know that the text is there, and won't do anything to preserve or clear the text, so the text may be overwritten or shifted, etc. So it can be done, if there is acceptance of Readline's limitations.
Sorry, I just have to ask a follow up question...
For quick testing I've done something like this:
...
-- build prompt...
local saveCursorPos = "\033[s"
local restoreCursorPos = "\033[u"
prompt = prompt .. saveCursorPos .. " Bla" .. restoreCursorPos
return prompt
Result looks like this:
So, the escape codes were not recognized.
I tried to surround them by \001 and \002 like this local saveCursorPos = "\001\033[s\002"
and this local restoreCursorPos = "\001\033[u\002"
as described in ANSI escape codes in the prompt string
, but didn't worked either.
I'm using escape codes for coloring without problems inside the string, as you can guess from my screenshot.
local saveCursorPos = "\033[s" local restoreCursorPos = "\033[u" prompt = prompt .. saveCursorPos .. " Bla" .. restoreCursorPos
... the escape codes were not recognized.
What terminal host is being used? The terminal host might not support those escape codes. Windows Terminal, ConEmu, ConsoleZ, and native VT support in Windows all support them. But if some other terminals don't, then the new clink-select-complete
command will likely have a garbled display, and I will need to rewrite how it moves the cursor around.
If your preferred terminal doesn't support CSI s/u then rearrange the prompt to not use them. First do the right side of the last line of the prompt, then use "\r"
to move the cursor to column 1, then do the left side of the last line of the prompt.
This is general ANSI escape code stuff, and isn't per se about Clink. I mention that only because it means generic ANSI escape code examples and prompt examples can be used with Clink, because there's not really any Clink-specific stuff going on here. For example, if there's a prompt configuration from oh-my-posh or another prompt-generating program, you can examine the ANSI escape codes they generate, and use those. Or possibly just invoke the program to produce the prompt (see the oh-my-posh example in the Clink docs).
I tried to surround them by \001 and \002 like this
local saveCursorPos = "\001\033[s\002"
and thislocal restoreCursorPos = "\001\033[u\002"
as described inANSI escape codes in the prompt string
, but didn't worked either.
That sounds right: The \001 and \002 characters are just so that Readline doesn't need to parse ANSI escape codes itself, so that it can exclude them from counting the visible length of the prompt text. Clink automatically adds them for all ANSI escape codes that Clink handles (and even for some that Clink doesn't handle).
By the way: Do you actually mind discussing such topics in git issues? If yes... I also like to write mails. Just drop me a line @ git@mail.flaemig42.de
Thank you for offering -- I think my email address is public in my github profile (let me know if it isn't). I'm generally happy to discuss in issues: they are searchable, which means in the future others can benefit from the conversations.
What terminal host is being used? The terminal host might not support those escape codes.
I'm using Windows Terminal Preview v1.10.1933.0. I will check if there is a issue reported regarding these two escape codes within the Windows Terminal community.
Problem found: The Escape-Sequence in
local saveCursorPos = "\033[s"
local restoreCursorPos = "\033[u"
is wrong, has to be \x1b
:
local saveCursorPos = "\x1b[s"
local restoreCursorPos = "\x1b[u"
Result:
My first source was https://tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html. Maybe bash uses different escape sequences...
Problem found: The Escape-Sequence in
local saveCursorPos = "\033[s" local restoreCursorPos = "\033[u"
is wrong, has to be
\x1b
:local saveCursorPos = "\x1b[s" local restoreCursorPos = "\x1b[u"
I'm glad you got it working. But the details don't sound right.
The "\033"
is syntax for an octal (base 8) character. Octal 33 is decimal 27, which is the Escape character.
The "\x1b"
is syntax for a hexadecimal (base 16) character. Hex 1b is decimal 27, which is the Escape character.
They're the same... 🙃
I wonder if there might have been another detail that changed somewhere in some of the code that wasn't listed.
I wonder if there might have been another detail that changed somewhere in some of the code that wasn't listed.
Here you are. I think there is no big deal about the cursor positioning stuff...
local green = "\x1b[92m"
local brightYellow = "\x1b[93m"
local yellow = "\x1b[33m"
local cyan = "\x1b[36m"
local red = "\x1b[91m"
local normal = "\x1b[m"
local color = green
local saveCursorPos = "\x1b[s"
local restoreCursorPos = "\x1b[u"
local setCursorPos1 = "\x1b["
local setCursorPos2 = "C"
local branchString = ""
-- status git repo
local sgr = green
local function get_git_dir(dir)
-- Check if the current directory is in a git repo.
local child
repeat
if os.isdir(path.join(dir, ".git")) then
return dir
end
-- Walk up one level to the parent directory.
dir,child = path.toparent(dir)
-- If child is empty, we've reached the top.
until (not child or child == "")
return nil
end
local function get_git_branch()
-- Get the current git branch name.
local file = io.popen("git branch --show-current 2>nul")
local branch = file:read("*a"):match("(.+)\n")
file:close()
return branch
end
local function get_git_status()
-- The io.popenyield API is like io.popen, but it yields until the output is
-- ready to be read.
local file = io.popenyield("git --no-optional-locks status --porcelain 2>nul")
local status = false
local lineLen = 0
for line in file:lines() do
-- If there's any output, the status is not clean. Since this example
-- doesn't analyze the details, it can stop once it knows there's any
-- output at all.
status = true
break
end
file:close()
return status
end
-- A prompt filter that discards any prompt so far and sets the
-- prompt to the current working directory. An ANSI escape code
-- colors it brightYellow.
local my_prompt = clink.promptfilter(30)
function my_prompt:filter(prompt)
dir = os.getcwd()
console.settitle(dir)
local lastReturnValue = os.getenv('ERRORLEVEL')
if lastReturnValue ~= "0" then
color = red
else
color = green
end
dirString = color.."<"..brightYellow..dir..color..">"
dateString = color.."<"..brightYellow..os.date("%x")..color..">"
lastReturnValueString = color.."-<"..brightYellow.."↳ "..lastReturnValue..color..">-"
userName = color.."<"..brightYellow..os.getenv('USERNAME')..color.." @ "..brightYellow..os.getenv('COMPUTERNAME')..color..">-<"
branchStringOffset = 0
-- Do nothing if not a git repo.
local gitDir = get_git_dir(os.getcwd())
if not gitDir then
-- do nothing
branchString = ""
sgr = green
else
-- Reset the cached status if in a different repo.
if prev_dir ~= gitDir then
prev_status = nil
prev_dir = gitDir
end
branch=get_git_branch()
if not branch or branch == "" then
branchString = ""
sgr = green
else
-- Start a coroutine to get git status, and returns nil immediately. The
-- coroutine runs in the background, and triggers prompt filtering again
-- when it completes. After it completes the return value here will be the
-- result from get_git_status().
local status = clink.promptcoroutine(get_git_status)
-- If no status yet, use the status from the previous prompt.
if status == nil then
status = prev_status
else
prev_status = status
end
-- Choose color for the git branch name: green if status is clean, brightYellow
-- if status is not clean, or default color if status isn't known yet.
if status ~= nil then
sgr = status and yellow or color
if status == true then
branchString = red.."["..brightYellow..branch..red.."]"..color
else
branchString = "["..brightYellow..branch..color.."]"
end
end
end
end
--
-- first line
--
promptFirstLine = color..">-"..dateString.."-"..dirString.."-"..branchString
-- calculate visible characters of the first line of the prompt
promptFirstLineLength = console.cellcount(promptFirstLine) + console.cellcount(userName)
-- create fillstring depending on current stringlength
fillString = ""
for i=promptFirstLineLength, console.getwidth()-1, 1 do
fillString = fillString.."-"
end
-- complete first line: date, dir, branch (optional), username
promptFirstLine = "\n"..promptFirstLine..fillString..userName.."\n"
--
-- second line
--
promptSecondLine = ">-<"..brightYellow..os.date(" %X ")..color..">-"..sgr.."|> "
-- calculate visible characters of the second line of the prompt
promptSecondLineLength = console.cellcount(promptSecondLine) + console.cellcount(lastReturnValueString)
-- calculate number of cursorposition movements to the right
local n = console.getwidth() - promptSecondLineLength - 1
-- complete second line (putting last returnValue to the far right)
promptSecondLine = promptSecondLine..saveCursorPos..setCursorPos1..n..setCursorPos2..lastReturnValueString.."<"..restoreCursorPos
prompt = promptFirstLine..promptSecondLine
return prompt
end
-- Alias funcion
local function alias(short, long)
os.execute('doskey ' .. short .. '=' .. long)
end
-- create aliases
alias('l', 'dir /B /P')
alias('ll', 'dir /D /P')
alias('lll', 'dir')
alias('gitl', 'git log --graph --color --decorate=auto --oneline')
alias('gitll', 'git log --graph --color --decorate=auto --name-status --abbrev-commit')
alias('top', 'ntop -s CPU%')
alias('stop', 'sudo ntop -s CPU%')
alias('cat', 'bat $1')
alias('type', 'bat $1')
Oh wow Lua is weird compared to other languages:
Under section 3.1 Lexical Conventions the Lua manual states:
A byte in a literal string can also be specified by its numerical value. This can be done with the escape sequence \xXX, where XX is a sequence of exactly two hexadecimal digits, or with the escape sequence \ddd, where ddd is a sequence of up to three decimal digits. (Note that if a decimal escape is to be followed by a digit, it must be expressed using exactly three digits.) Strings in Lua can contain any 8-bit value, including embedded zeros, which can be specified as '\0'.
In other words:
\033
is octal which translates to 27 decimal.\033
is decimal which is 33 decimal.So in Lua the Escape character must be written as \027
or \x1b
.
The \xXX syntax is pretty universal across programming languages, so it's good for portability. The main reason I avoid the \ddd syntax is that octal is rarely used and so numbers in octal are unfamiliar to read, and C/C++ don't have a literal string escape for using decimal to represent a character, thus hexadecimal is generally the way to go for clarity.
Also, sorry but it goes haywire when the input line reaches the right justified portion. Because Readline counted the right-justified portion as though it had been left-justified, so Readline is confused where the right margin is.
I don't think this will be able to work very soon, but I will explore how it could be supported in the future.
Oh, good: zsh has a special separate configurable prompt for the right-justified prompt segment. I was hoping for that; it shouldn't be much work to hook it up as a separate prompt string. Maybe sometime in the next week or two.
I don't think this will be able to work very soon, but I will explore how it could be supported in the future.
I'm pretty fine with my current solution. It is only the return code that is displayed to the right of the line. It does not matter if this string is overwritten by user input on the command line or if it disappears after starting typing.
it shouldn't be much work to hook it up as a separate prompt string. Maybe sometime in the next week or two.
I can't estimate the efforts needed for that, but in my point of view it is more eyecandy and not as important...
I think you haven't tried typing enough at the input line to reach the right-justified text.
Try doing it. You'll see how badly it gets garbled.
Try doing it. You'll see how badly it gets garbled.
I got your point
My current work-around:\ Using a 27" display with windows terminal in fullscreen 😆 So there is enough input line available 😃
Well that turned out to be easy, after all. A few small surgical changes in Readline, and a little plumbing code in Clink. The Readline changes should even be shareable with Chet Ramey for potential inclusion in the public Readline library, and maybe even in bash, if he wants to add support there.
Changes to Readline code: 90067711b3617b3e4b3504108c94958511ad5a81, 33520f929baad2a3b9e951a00dc92e2fe21824d5 Changes to Clink code: 5d24b0f5baf3d593641a0663ec5604dcdadca244, f6e9e4bff5e46222b01c192f22e599550ac8c6fa, 3ed09e642269dbb4244bfda9d64960e81cbf420c
I updated the script to use :rightfilter()
for right side prompt.
Here is a clink.1.2.24.0ed435.zip with a pre-release build if you want to try it out.
I'll publish a release build sometime next week.
local green = "\x1b[92m"
local brightYellow = "\x1b[93m"
local yellow = "\x1b[33m"
local cyan = "\x1b[36m"
local red = "\x1b[91m"
local normal = "\x1b[m"
local color = green
local branchString = ""
-- status git repo
local sgr = green
local function get_git_dir(dir)
-- Check if the current directory is in a git repo.
local child
repeat
if os.isdir(path.join(dir, ".git")) then
return dir
end
-- Walk up one level to the parent directory.
dir,child = path.toparent(dir)
-- If child is empty, we've reached the top.
until (not child or child == "")
return nil
end
local function get_git_branch()
-- Get the current git branch name.
local file = io.popen("git branch --show-current 2>nul")
local branch = file:read("*a"):match("(.+)\n")
file:close()
return branch
end
local function get_git_status()
-- The io.popenyield API is like io.popen, but it yields until the output is
-- ready to be read.
local file = io.popenyield("git --no-optional-locks status --porcelain 2>nul")
local status = false
local lineLen = 0
for line in file:lines() do
-- If there's any output, the status is not clean. Since this example
-- doesn't analyze the details, it can stop once it knows there's any
-- output at all.
status = true
break
end
file:close()
return status
end
-- A prompt filter that discards any prompt so far and sets the
-- prompt to the current working directory. An ANSI escape code
-- colors it brightYellow.
local my_prompt = clink.promptfilter(130)
function my_prompt:filter(prompt)
dir = os.getcwd()
console.settitle(dir)
local lastReturnValue = os.getenv('ERRORLEVEL')
if lastReturnValue ~= "0" then
color = red
else
color = green
end
dirString = color.."<"..brightYellow..dir..color..">"
dateString = color.."<"..brightYellow..os.date("%x")..color..">"
userName = color.."<"..brightYellow..os.getenv('USERNAME')..color.." @ "..brightYellow..os.getenv('COMPUTERNAME')..color..">-<"
branchStringOffset = 0
-- Do nothing if not a git repo.
local gitDir = get_git_dir(os.getcwd())
if not gitDir then
-- do nothing
branchString = ""
sgr = green
else
-- Reset the cached status if in a different repo.
if prev_dir ~= gitDir then
prev_status = nil
prev_dir = gitDir
end
branch=get_git_branch()
if not branch or branch == "" then
branchString = ""
sgr = green
else
-- Start a coroutine to get git status, and returns nil immediately. The
-- coroutine runs in the background, and triggers prompt filtering again
-- when it completes. After it completes the return value here will be the
-- result from get_git_status().
local status = clink.promptcoroutine(get_git_status)
-- If no status yet, use the status from the previous prompt.
if status == nil then
status = prev_status
else
prev_status = status
end
-- Choose color for the git branch name: green if status is clean, brightYellow
-- if status is not clean, or default color if status isn't known yet.
if status ~= nil then
sgr = status and yellow or color
if status == true then
branchString = red.."["..brightYellow..branch..red.."]"..color
else
branchString = "["..brightYellow..branch..color.."]"
end
end
end
end
--
-- first line
--
promptFirstLine = color..">-"..dateString.."-"..dirString.."-"..branchString
-- calculate visible characters of the first line of the prompt
promptFirstLineLength = console.cellcount(promptFirstLine) + console.cellcount(userName)
-- create fillstring depending on current stringlength
fillString = ""
for i=promptFirstLineLength, console.getwidth()-1, 1 do
fillString = fillString.."-"
end
-- complete first line: date, dir, branch (optional), username
promptFirstLine = "\n"..promptFirstLine..fillString..userName.."\n"
--
-- second line
--
promptSecondLine = color..">-<"..brightYellow..os.date(" %X ")..color..">-"..sgr.."|> "
prompt = promptFirstLine..promptSecondLine
return prompt
end
function my_prompt:rightfilter(prompt)
local lastReturnValue = os.getenv('ERRORLEVEL')
if lastReturnValue ~= "0" then
color = red
else
color = green
end
return color.."-<"..brightYellow.."↳ "..lastReturnValue..color..">-<"
end
-- Workaround for Readline's assumption that there is no right-justified text.
local function show_hangover()
if promptHangover then
clink.print(promptHangover)
end
end
clink.onbeginedit(show_hangover)
-- Alias funcion
local function alias(short, long)
os.execute('doskey ' .. short .. '=' .. long)
end
-- create aliases
alias('l', 'dir /B /P')
alias('ll', 'dir /D /P')
alias('lll', 'dir')
alias('gitl', 'git log --graph --color --decorate=auto --oneline')
alias('gitll', 'git log --graph --color --decorate=auto --name-status --abbrev-commit')
alias('top', 'ntop -s CPU%')
alias('stop', 'sudo ntop -s CPU%')
alias('cat', 'bat $1')
alias('type', 'bat $1')
v1.2.24 includes the %CLINK_RPROMPT%
and :rightfilter()
changes.
I can't update the Clink site yet, because a recent change inside GitHub Pages has broken how sites are updated. So until GitHub Pages gets fixed, the only way to access new versions of Clink is from the releases page. Hopefully the GitHub Pages outage won't be more than a few days.
I'm always using the relases page.\ Until github gets the pages stuff fixed, you can fix a small issue with your docu. You are linking to https://github.com/collink/clink-git-extensions, which causes a 404 error ;)
I'm always using the relases page. Until github gets the pages stuff fixed, you can fix a small issue with your docu. You are linking to https://github.com/collink/clink-git-extensions, which causes a 404 error ;)
Are you are looking at the v1.2.23 docs?
The v1.2.24 docs should not reference the collink repo anymore -- apparently that user deleted their repo (maybe even due to having been linked from the Clink docs, I don't know).
Is there a way to create a prompt where some symbols are positioned to the far right of the prompt (but in the same line), like in the screenshot?
Something like moving cursor to the right, output some symbols, and after that moving the cursor back to the start column?