aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-08-30 22:42:38 +0200
committerhachem <im@hachem.wtf>2026-08-30 22:42:38 +0200
commitdc384ed13dc22d2640a20fbb030531b7f5d97502 (patch)
tree2d56c15cce579313a794462358300f3e6fa37bb1
parentf099a82eb2b2a6bb64cd51d98ab027f4ce45078c (diff)
feat: add rust support lmao
-rw-r--r--src/debugger/debugger.cpp77
-rw-r--r--src/ui/source_view.cpp77
2 files changed, 141 insertions, 13 deletions
diff --git a/src/debugger/debugger.cpp b/src/debugger/debugger.cpp
index cd1eac1..97af0e1 100644
--- a/src/debugger/debugger.cpp
+++ b/src/debugger/debugger.cpp
@@ -7,6 +7,8 @@
#include <algorithm>
#include <array>
+#include <cctype>
+#include <cstdio>
#include <set>
#include <source_location>
@@ -87,6 +89,79 @@ namespace Hsdbg
return text != nullptr && text[0] != '\0' ? std::string(text) : std::string(fallback);
}
+ // runs a command and hands back its stdout, trimmed; empty if it could
+ // not be spawned (used to ask the toolchain where it lives)
+ auto capture_command(const char* command) -> std::string
+ {
+#ifdef HSDBG_WINDOWS
+ FILE* pipe = _popen(command, "r");
+#else
+ FILE* pipe = popen(command, "r");
+#endif
+ if (pipe == nullptr)
+ return {};
+
+ std::string output;
+ std::array<char, 256> chunk{};
+
+ while (std::fgets(chunk.data(), static_cast<int>(chunk.size()), pipe) != nullptr)
+ output += chunk.data();
+
+#ifdef HSDBG_WINDOWS
+ _pclose(pipe);
+#else
+ pclose(pipe);
+#endif
+
+ while (!output.empty() && std::isspace(static_cast<unsigned char>(output.back())) != 0)
+ output.pop_back();
+
+ return output;
+ }
+
+ // rust ships lldb data formatters (the same ones rust-lldb sources) that
+ // teach lldb how to print String, Vec, Option, enums and friends. without
+ // them those show as raw structs, so pull them in if a toolchain is around.
+ // harmless for c/c++ targets, and a no-op if lldb has no python scripting
+ auto load_rust_formatters(lldb::SBDebugger& debugger) -> void
+ {
+ const std::string sysroot = capture_command("rustc --print sysroot 2>/dev/null");
+
+ if (sysroot.empty())
+ return;
+
+ const std::filesystem::path etc =
+ std::filesystem::path(sysroot) / "lib" / "rustlib" / "etc";
+
+ std::error_code error;
+ if (!std::filesystem::exists(etc / "lldb_commands", error))
+ return;
+
+ lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter();
+ lldb::SBCommandReturnObject result;
+
+ const std::string import =
+ std::format("command script import \"{}\"", (etc / "lldb_lookup.py").string());
+ interpreter.HandleCommand(import.c_str(), result);
+
+ if (!result.Succeeded())
+ {
+ Log::warn("debugger: rust formatters unavailable ({})",
+ text_or(result.GetError(), "lldb has no python scripting"));
+ return;
+ }
+
+ const std::string source =
+ std::format("command source \"{}\"", (etc / "lldb_commands").string());
+ interpreter.HandleCommand(source.c_str(), result);
+
+ if (result.Succeeded())
+ Log::info("debugger: loaded rust type formatters from {}", etc.string());
+ else
+ Log::warn("debugger: could not load rust formatters ({})",
+ text_or(result.GetError(), "unknown error"));
+ }
+
auto path_of(lldb::SBFileSpec spec) -> std::filesystem::path
{
if (!spec.IsValid())
@@ -463,6 +538,8 @@ namespace Hsdbg
m_session->listener = lldb::SBListener("hsdbg");
Log::info("debugger: {}", lldb::SBDebugger::GetVersionString());
+
+ load_rust_formatters(m_session->debugger);
}
Debugger::~Debugger()
diff --git a/src/ui/source_view.cpp b/src/ui/source_view.cpp
index c7fb606..96a150b 100644
--- a/src/ui/source_view.cpp
+++ b/src/ui/source_view.cpp
@@ -10,6 +10,7 @@
#include <algorithm>
#include <cctype>
#include <fstream>
+#include <optional>
#include <string_view>
#include <unordered_set>
@@ -37,6 +38,12 @@ namespace Hsdbg
return 0;
}
+ enum class Language : uint8_t
+ {
+ Cpp,
+ Rust,
+ };
+
auto is_word_start(char c) -> bool
{
return std::isalpha(static_cast<unsigned char>(c)) != 0 || c == '_';
@@ -47,9 +54,9 @@ namespace Hsdbg
return std::isalnum(static_cast<unsigned char>(c)) != 0 || c == '_';
}
- auto classify_word(std::string_view word) -> SyntaxKind
+ auto classify_word(std::string_view word, Language language) -> SyntaxKind
{
- static const std::unordered_set<std::string_view> keywords = {
+ static const std::unordered_set<std::string_view> cpp_keywords = {
"alignas", "alignof", "and", "and_eq", "asm", "bitand", "bitor", "break",
"case", "catch", "class", "co_await", "co_return", "co_yield", "compl",
"concept", "const", "consteval", "constexpr", "constinit", "const_cast",
@@ -64,13 +71,32 @@ namespace Hsdbg
"xor", "xor_eq",
};
- static const std::unordered_set<std::string_view> types = {
+ static const std::unordered_set<std::string_view> cpp_types = {
"auto", "bool", "char", "char8_t", "char16_t", "char32_t", "double", "float",
"int", "int8_t", "int16_t", "int32_t", "int64_t", "intptr_t", "long",
"ptrdiff_t", "short", "signed", "size_t", "ssize_t", "uint8_t", "uint16_t",
"uint32_t", "uint64_t", "uintptr_t", "unsigned", "void", "wchar_t",
};
+ static const std::unordered_set<std::string_view> rust_keywords = {
+ "as", "async", "await", "break", "const", "continue", "crate", "dyn", "else",
+ "enum", "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop",
+ "match", "mod", "move", "mut", "pub", "ref", "return", "self", "Self",
+ "static", "struct", "super", "trait", "true", "type", "union", "unsafe",
+ "use", "where", "while", "box", "dyn", "macro_rules", "yield",
+ };
+
+ static const std::unordered_set<std::string_view> rust_types = {
+ "bool", "char", "str", "String", "i8", "i16", "i32", "i64", "i128", "isize",
+ "u8", "u16", "u32", "u64", "u128", "usize", "f32", "f64", "Vec", "Option",
+ "Result", "Box", "Rc", "Arc", "Cell", "RefCell", "HashMap", "HashSet",
+ "BTreeMap", "BTreeSet", "VecDeque", "Cow",
+ };
+
+ const bool rust = language == Language::Rust;
+ const auto& keywords = rust ? rust_keywords : cpp_keywords;
+ const auto& types = rust ? rust_types : cpp_types;
+
if (keywords.contains(word))
return SyntaxKind::Keyword;
@@ -94,7 +120,7 @@ namespace Hsdbg
// splits every line into contiguous coloured spans. the whole file is
// walked in order so a block comment opened on one line stays open on the
// next, which a per-line pass could not know
- auto highlight_lines(const std::vector<std::string>& lines)
+ auto highlight_lines(const std::vector<std::string>& lines, Language language)
-> std::vector<std::vector<SourceSpan>>
{
std::vector<std::vector<SourceSpan>> out(lines.size());
@@ -170,6 +196,22 @@ namespace Hsdbg
continue;
}
+ // a rust lifetime ('a) looks like an unterminated char literal,
+ // so peel it off as an identifier before the string handling
+ if (c == '\'' && language == Language::Rust && at + 1 < size &&
+ is_word_start(line[at + 1]) && !(at + 2 < size && line[at + 2] == '\''))
+ {
+ flush_default(at);
+ const uint32_t start = at++;
+
+ while (at < size && is_word(line[at]))
+ ++at;
+
+ emit(start, at - start, SyntaxKind::Default);
+ run = at;
+ continue;
+ }
+
if (c == '"' || c == '\'')
{
flush_default(at);
@@ -197,7 +239,7 @@ namespace Hsdbg
continue;
}
- if (c == '#' && only_space_before(line, at))
+ if (c == '#' && language == Language::Cpp && only_space_before(line, at))
{
flush_default(at);
const uint32_t start = at++;
@@ -222,7 +264,7 @@ namespace Hsdbg
const char d = line[at];
if (std::isalnum(static_cast<unsigned char>(d)) != 0 ||
- d == '.' || d == '\'')
+ d == '.' || d == '\'' || d == '_')
++at;
else
break;
@@ -240,8 +282,8 @@ namespace Hsdbg
while (at < size && is_word(line[at]))
++at;
- if (const SyntaxKind kind =
- classify_word(std::string_view(line).substr(start, at - start));
+ if (const SyntaxKind kind = classify_word(
+ std::string_view(line).substr(start, at - start), language);
kind != SyntaxKind::Default)
{
flush_default(start);
@@ -261,9 +303,9 @@ namespace Hsdbg
return out;
}
- auto is_highlightable(const std::filesystem::path& path) -> bool
+ auto language_of(const std::filesystem::path& path) -> std::optional<Language>
{
- static const std::unordered_set<std::string_view> extensions = {
+ static const std::unordered_set<std::string_view> cpp = {
".c", ".h", ".cc", ".cp", ".cpp", ".cxx", ".c++", ".hh", ".hpp", ".hxx",
".h++", ".inl", ".ipp", ".m", ".mm", ".cu", ".cuh",
};
@@ -274,7 +316,13 @@ namespace Hsdbg
return static_cast<char>(std::tolower(ch));
});
- return extensions.contains(ext);
+ if (ext == ".rs")
+ return Language::Rust;
+
+ if (cpp.contains(ext))
+ return Language::Cpp;
+
+ return std::nullopt;
}
// the dot sits in the gutter with this much room on either side, and the
@@ -310,10 +358,13 @@ namespace Hsdbg
lines.push_back(std::move(line));
}
+ const std::optional<Language> language = language_of(path);
+
m_path = path;
m_lines = std::move(lines);
- m_highlight = is_highlightable(path);
- m_spans = m_highlight ? highlight_lines(m_lines) : std::vector<std::vector<SourceSpan>>{};
+ m_highlight = language.has_value();
+ m_spans = language ? highlight_lines(m_lines, *language)
+ : std::vector<std::vector<SourceSpan>>{};
m_path_input = path.string();
m_error.clear();
m_highlighted_line = 0;