diff --git a/src/ui/diff_viewer.cpp b/src/ui/diff_viewer.cpp index 28a21d2..3409d79 100644 --- a/src/ui/diff_viewer.cpp +++ b/src/ui/diff_viewer.cpp @@ -401,7 +401,7 @@ struct MinimapEntry { }; void drawMinimap(const std::vector& entries, float scale, - float visible_ratio, float scroll_ratio) { + float rendered_content_height, float visible_start, float visible_height) { const ImVec2 minimum = ImGui::GetCursorScreenPos(); const ImVec2 size = ImGui::GetContentRegionAvail(); ImGui::InvisibleButton("##code_minimap", size); @@ -426,8 +426,12 @@ void drawMinimap(const std::vector& entries, float scale, entries[index].color, scaled(1.0f, scale)); } - const float viewport_height = std::clamp(size.y * visible_ratio, scaled(18.0f, scale), size.y); - const float viewport_y = minimum.y + (size.y - viewport_height) * std::clamp(scroll_ratio, 0.0f, 1.0f); + const float content_height = std::max(rendered_content_height, 1.0f); + const float clamped_visible_height = std::clamp(visible_height, 0.0f, content_height); + const float clamped_visible_start = std::clamp(visible_start, 0.0f, std::max(0.0f, content_height - clamped_visible_height)); + const float viewport_height = std::clamp(size.y * (clamped_visible_height / content_height), + scaled(18.0f, scale), size.y); + const float viewport_y = minimum.y + size.y * (clamped_visible_start / content_height); draw->AddRectFilled({minimum.x + scaled(1.0f, scale), viewport_y}, {minimum.x + size.x - scaled(11.0f, scale), viewport_y + viewport_height}, IM_COL32(120, 146, 198, 45), scaled(2.0f, scale)); @@ -435,6 +439,27 @@ void drawMinimap(const std::vector& entries, float scale, {minimum.x + size.x - scaled(11.0f, scale), viewport_y + viewport_height}, IM_COL32(120, 146, 198, 160), scaled(2.0f, scale)); } + +void drawCodeLine(const std::string& text, SyntaxLanguage language, SyntaxState& syntax, + float scale, ImU32 background = IM_COL32(0, 0, 0, 0), float left_gutter = 0.0f, + float minimum_width = 0.0f) { + const float row_height = scaled(21.0f, scale); + const ImVec2 start = ImGui::GetCursorScreenPos(); + const float width = std::max(minimum_width, ImGui::CalcTextSize(text.c_str()).x + left_gutter + scaled(12.0f, scale)); + ImGui::InvisibleButton("##code_line", {width, row_height}); + const ImVec2 minimum = ImGui::GetItemRectMin(); + const ImVec2 maximum = ImGui::GetItemRectMax(); + ImDrawList* draw = ImGui::GetWindowDrawList(); + if ((background & IM_COL32_A_MASK) != 0) + draw->AddRectFilled(minimum, maximum, background); + drawSyntaxText(draw, {start.x + left_gutter, start.y + scaled(2.0f, scale)}, text, language, syntax); +} + +void drawCodeLineNumber(int value, float x, float y, ImU32 color) { + if (value <= 0) return; + const std::string text = std::to_string(value); + ImGui::GetWindowDrawList()->AddText({x, y}, color, text.c_str()); +} } void DiffViewer::open(RepositoryView& repository, GitManager& manager, const std::string& path, @@ -691,12 +716,17 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, AvatarCac const bool show_minimap = mode_ == Mode::diff || mode_ == Mode::file; const float minimap_width = scaled(56.0f, scale); float main_scroll_y = 0.0f; - float main_scroll_max_y = 0.0f; float main_window_height = 0.0f; + float rendered_content_height = 0.0f; std::vector minimap_entries; if (show_minimap) { + minimap_entries.push_back({0, IM_COL32(0, 0, 0, 0)}); if (mode_ == Mode::diff) { for (size_t hunk_index = 0; hunk_index < hunks_.size(); ++hunk_index) { + if (hunk_index > 0) { + minimap_entries.push_back({0, IM_COL32(0, 0, 0, 0)}); + minimap_entries.push_back({0, IM_COL32(0, 0, 0, 0)}); + } minimap_entries.push_back({hunks_[hunk_index].header.size(), IM_COL32(128, 133, 141, 255)}); for (const Line& line : hunks_[hunk_index].lines) { const ImU32 color = line.kind == LineKind::added ? IM_COL32(87, 190, 112, 255) : @@ -719,6 +749,8 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, AvatarCac const bool use_code_font = code_font && mode_ != Mode::history; if (use_code_font) ImGui::PushFont(code_font, 0.0f); const float row_height = scaled(21, scale); + const float content_width = std::max(ImGui::GetContentRegionAvail().x, scaled(200.0f, scale)); + const SyntaxLanguage language = languageForPath(path_); // Keep the first source row clear of the toolbar and aligned with a normal row inset. ImGui::Dummy({0.0f, row_height}); @@ -762,15 +794,23 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, AvatarCac } ImGui::Separator(); ImGui::Dummy({0.0f, scaled(4.0f, scale)}); - std::string hunk_text; - for (size_t line_index = 0; line_index < hunks_[hunk_index].lines.size(); ++line_index) { - if (line_index) hunk_text += '\n'; - hunk_text += hunks_[hunk_index].lines[line_index].raw; + const float number_column = scaled(44.0f, scale); + const float code_gutter = number_column * 2.0f + scaled(14.0f, scale); + for (const Line& line : hunks_[hunk_index].lines) { + ImGui::PushID(line_id++); + const ImVec2 line_minimum = ImGui::GetCursorScreenPos(); + const ImU32 background = line.kind == LineKind::added ? IM_COL32(30, 68, 46, 170) : + line.kind == LineKind::removed ? IM_COL32(86, 38, 42, 170) : IM_COL32(0, 0, 0, 0); + drawCodeLine(line.text, language, + line.kind == LineKind::removed ? old_syntax : new_syntax, + scale, background, code_gutter, content_width); + const float text_y = line_minimum.y + scaled(2.0f, scale); + drawCodeLineNumber(line.old_number, line_minimum.x + scaled(6.0f, scale), text_y, + IM_COL32(126, 132, 142, 255)); + drawCodeLineNumber(line.new_number, line_minimum.x + number_column + scaled(6.0f, scale), text_y, + IM_COL32(126, 132, 142, 255)); + ImGui::PopID(); } - const float hunk_height = std::max(row_height * 3.0f, - row_height * static_cast(hunks_[hunk_index].lines.size()) + scaled(10.0f, scale)); - drawSelectableTextBlock(("##diff_hunk_text" + std::to_string(line_id++)).c_str(), - hunk_text, {-1, hunk_height}); ImGui::PopID(); } if (pending_hunk >= 0 && pending_hunk < static_cast(hunks_.size())) { @@ -798,23 +838,36 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, AvatarCac if (blame_lines_.empty()) ImGui::TextDisabled("No blame data is available for this file."); } else { const std::vector* lines = mode_ == Mode::file ? &file_lines_ : &history_lines_; - drawSelectableTextBlock(mode_ == Mode::file ? "##file_text" : "##history_text", - joinLines(*lines), {-1, -1}); + if (mode_ == Mode::file) { + SyntaxState syntax; + const float code_gutter = scaled(52.0f, scale); + for (size_t index = 0; index < lines->size(); ++index) { + ImGui::PushID(static_cast(index)); + const ImVec2 line_minimum = ImGui::GetCursorScreenPos(); + drawCodeLine((*lines)[index], language, syntax, scale, IM_COL32(0, 0, 0, 0), + code_gutter, content_width); + drawCodeLineNumber(static_cast(index + 1), line_minimum.x + scaled(6.0f, scale), + line_minimum.y + scaled(2.0f, scale), IM_COL32(126, 132, 142, 255)); + ImGui::PopID(); + } + } else { + drawSelectableTextBlock("##history_text", joinLines(*lines), {-1, -1}); + } if (lines->empty()) ImGui::TextDisabled("No data is available for this view."); } main_scroll_y = ImGui::GetScrollY(); - main_scroll_max_y = ImGui::GetScrollMaxY(); main_window_height = ImGui::GetWindowHeight(); + rendered_content_height = ImGui::GetCursorPosY(); if (use_code_font) ImGui::PopFont(); ImGui::EndChild(); if (show_minimap) { ImGui::SameLine(0, scaled(6.0f, scale)); - ImGui::BeginChild("diff_content_minimap", {minimap_width, -1}, ImGuiChildFlags_None, + const float minimap_line_height = scaled(2.0f, scale); + const float minimap_height = std::min(main_window_height, + std::max(scaled(36.0f, scale), static_cast(minimap_entries.size()) * minimap_line_height + scaled(8.0f, scale))); + ImGui::BeginChild("diff_content_minimap", {minimap_width, minimap_height}, ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); - const float visible_ratio = main_scroll_max_y <= 0.0f - ? 1.0f : std::clamp(main_window_height / (main_window_height + main_scroll_max_y), 0.0f, 1.0f); - const float scroll_ratio = main_scroll_max_y <= 0.0f ? 0.0f : main_scroll_y / main_scroll_max_y; - drawMinimap(minimap_entries, scale, visible_ratio, scroll_ratio); + drawMinimap(minimap_entries, scale, rendered_content_height, main_scroll_y, main_window_height); ImGui::EndChild(); } ImGui::EndChild();