|
@@ -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;
|
|
|
}
|
|
|
|