mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-01-07 19:57:45 +03:00
Shell: Tab completion for programs in PATH
This patch adds a function to LineEditor that takes the current shell buffer, searches PATH for the first program that starts with that buffer and then compares that to any other programs starting with the buffer to remove any mismatching characters off the end. The result is appended to the buffer. This may be faster with a data structure but that seems overkill.
This commit is contained in:
parent
4463adc0ff
commit
dccab569d2
Notes:
sideshowbarker
2024-07-19 12:06:03 +09:00
Author: https://github.com/willmcpherson2 🔰 Commit: https://github.com/SerenityOS/serenity/commit/dccab569d2f Pull-request: https://github.com/SerenityOS/serenity/pull/567 Reviewed-by: https://github.com/awesomekling
@ -37,6 +37,60 @@ void LineEditor::append(const String& string)
|
||||
m_cursor = m_buffer.size();
|
||||
}
|
||||
|
||||
void LineEditor::tab_complete_first_token()
|
||||
{
|
||||
auto input = String::copy(m_buffer);
|
||||
|
||||
String path = getenv("PATH");
|
||||
if (path.is_empty())
|
||||
return;
|
||||
auto directories = path.split(':');
|
||||
|
||||
String match;
|
||||
|
||||
// Go through the files in PATH.
|
||||
for (const auto& directory : directories) {
|
||||
CDirIterator programs(directory.characters(), CDirIterator::SkipDots);
|
||||
while (programs.has_next()) {
|
||||
String program = programs.next_path();
|
||||
if (!program.starts_with(input))
|
||||
continue;
|
||||
|
||||
// Check that the file is an executable program.
|
||||
struct stat program_status;
|
||||
StringBuilder program_path;
|
||||
program_path.append(directory.characters());
|
||||
program_path.append('/');
|
||||
program_path.append(program.characters());
|
||||
int stat_error = stat(program_path.to_string().characters(), &program_status);
|
||||
if (stat_error || !(program_status.st_mode & S_IXUSR))
|
||||
continue;
|
||||
|
||||
// Set `match` to the first one that starts with `input`.
|
||||
if (match.is_empty()) {
|
||||
match = program;
|
||||
} else {
|
||||
// Remove characters from the end of `match` if they're
|
||||
// different from another `program` starting with `input`.
|
||||
int i = input.length();
|
||||
while (i < match.length() && i < program.length() && match[i] == program[i])
|
||||
++i;
|
||||
match = match.substring(0, i);
|
||||
}
|
||||
|
||||
if (match.length() == input.length())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (match.is_empty())
|
||||
return;
|
||||
|
||||
// Then append `match` to the buffer, excluding the `input` part which is
|
||||
// already in the buffer.
|
||||
append(match.substring(input.length(), match.length() - input.length()).characters());
|
||||
}
|
||||
|
||||
String LineEditor::get_line(const String& prompt)
|
||||
{
|
||||
fputs(prompt.characters(), stdout);
|
||||
@ -174,7 +228,21 @@ String LineEditor::get_line(const String& prompt)
|
||||
}
|
||||
|
||||
if (ch == '\t') {
|
||||
// FIXME: Implement tab-completion.
|
||||
if (m_buffer.is_empty())
|
||||
continue;
|
||||
|
||||
bool is_first_token = true;
|
||||
for (const auto& character : m_buffer) {
|
||||
if (isspace(character)) {
|
||||
is_first_token = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Implement tab-completion for other tokens (paths).
|
||||
if (is_first_token)
|
||||
tab_complete_first_token();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCore/CDirIterator.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
class LineEditor {
|
||||
public:
|
||||
@ -16,6 +19,7 @@ public:
|
||||
private:
|
||||
void clear_line();
|
||||
void append(const String&);
|
||||
void tab_complete_first_token();
|
||||
void vt_save_cursor();
|
||||
void vt_restore_cursor();
|
||||
void vt_clear_to_end_of_line();
|
||||
|
Loading…
Reference in New Issue
Block a user