Serenity Operating System
at master 228 lines 8.1 kB view raw
1/* 2 * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "DiffViewer.h" 9#include <AK/Debug.h> 10#include <LibDiff/Hunks.h> 11#include <LibGUI/AbstractView.h> 12#include <LibGUI/Painter.h> 13#include <LibGUI/Scrollbar.h> 14#include <LibGfx/Color.h> 15#include <LibGfx/Font/FontDatabase.h> 16#include <LibGfx/Palette.h> 17 18namespace HackStudio { 19 20void DiffViewer::paint_event(GUI::PaintEvent& event) 21{ 22 GUI::Painter painter(*this); 23 painter.add_clip_rect(widget_inner_rect()); 24 painter.add_clip_rect(event.rect()); 25 painter.fill_rect(event.rect(), palette().color(background_role())); 26 painter.translate(frame_thickness(), frame_thickness()); 27 painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value()); 28 29 // Why we need to translate here again? We've already translated the painter. 30 // Anyways, it paints correctly so I'll leave it like this. 31 painter.fill_rect_with_dither_pattern( 32 separator_rect().translated(horizontal_scrollbar().value(), vertical_scrollbar().value()), 33 Gfx::Color::LightGray, 34 Gfx::Color::White); 35 36 size_t y_offset = 10; 37 size_t current_original_line_index = 0; 38 for (auto const& hunk : m_hunks) { 39 for (size_t i = current_original_line_index; i < hunk.original_start_line; ++i) { 40 draw_line(painter, m_original_lines[i], y_offset, LinePosition::Both, LineType::Normal); 41 y_offset += line_height(); 42 } 43 current_original_line_index = hunk.original_start_line + hunk.removed_lines.size(); 44 45 size_t left_y_offset = y_offset; 46 for (auto const& removed_line : hunk.removed_lines) { 47 draw_line(painter, removed_line, left_y_offset, LinePosition::Left, LineType::Diff); 48 left_y_offset += line_height(); 49 } 50 for (int i = 0; i < (int)hunk.added_lines.size() - (int)hunk.removed_lines.size(); ++i) { 51 draw_line(painter, "", left_y_offset, LinePosition::Left, LineType::Missing); 52 left_y_offset += line_height(); 53 } 54 55 size_t right_y_offset = y_offset; 56 for (auto const& added_line : hunk.added_lines) { 57 draw_line(painter, added_line, right_y_offset, LinePosition::Right, LineType::Diff); 58 right_y_offset += line_height(); 59 } 60 for (int i = 0; i < (int)hunk.removed_lines.size() - (int)hunk.added_lines.size(); ++i) { 61 draw_line(painter, "", right_y_offset, LinePosition::Right, LineType::Missing); 62 right_y_offset += line_height(); 63 } 64 65 VERIFY(left_y_offset == right_y_offset); 66 y_offset = left_y_offset; 67 } 68 for (size_t i = current_original_line_index; i < m_original_lines.size(); ++i) { 69 draw_line(painter, m_original_lines[i], y_offset, LinePosition::Both, LineType::Normal); 70 y_offset += line_height(); 71 } 72} 73 74void DiffViewer::draw_line(GUI::Painter& painter, DeprecatedString const& line, size_t y_offset, LinePosition line_position, LineType line_type) 75{ 76 size_t line_width = font().width(line); 77 78 constexpr size_t padding = 10; 79 size_t left_side_x_offset = padding; 80 size_t right_side_x_offset = separator_rect().x() + padding; 81 82 // FIXME: Long lines will overflow out of their side of the diff view 83 Gfx::IntRect left_line_rect { (int)left_side_x_offset, (int)y_offset, (int)line_width, (int)line_height() }; 84 Gfx::IntRect right_line_rect { (int)right_side_x_offset, (int)y_offset, (int)line_width, (int)line_height() }; 85 auto color = palette().color(foreground_role()); 86 87 if (line_position == LinePosition::Left || line_position == LinePosition::Both) { 88 painter.draw_text(left_line_rect, line, Gfx::TextAlignment::TopLeft, color); 89 if (line_type != LineType::Normal) { 90 Gfx::IntRect outline = { (int)left_side_x_offset, ((int)y_offset) - 2, separator_rect().x() - (int)(padding * 2), (int)line_height() }; 91 if (line_type == LineType::Diff) { 92 painter.fill_rect( 93 outline, 94 red_background()); 95 } 96 if (line_type == LineType::Missing) { 97 painter.fill_rect( 98 outline, 99 gray_background()); 100 } 101 } 102 } 103 if (line_position == LinePosition::Right || line_position == LinePosition::Both) { 104 painter.draw_text(right_line_rect, line, Gfx::TextAlignment::TopLeft, color); 105 if (line_type != LineType::Normal) { 106 Gfx::IntRect outline = { (int)right_side_x_offset, ((int)y_offset) - 2, frame_inner_rect().width() - separator_rect().x() - (int)(padding * 2) - 10, (int)line_height() }; 107 if (line_type == LineType::Diff) { 108 painter.fill_rect( 109 outline, 110 green_background()); 111 } 112 if (line_type == LineType::Missing) { 113 painter.fill_rect( 114 outline, 115 gray_background()); 116 } 117 } 118 } 119} 120 121size_t DiffViewer::line_height() const 122{ 123 return font().pixel_size_rounded_up() + 4; 124} 125 126Gfx::IntRect DiffViewer::separator_rect() const 127{ 128 return Gfx::IntRect { frame_inner_rect().width() / 2 - 2, 129 0, 130 4, 131 frame_inner_rect().height() }; 132} 133 134void DiffViewer::set_content(DeprecatedString const& original, DeprecatedString const& diff) 135{ 136 m_original_lines = split_to_lines(original); 137 m_hunks = Diff::parse_hunks(diff); 138 139 if constexpr (DIFF_DEBUG) { 140 for (size_t i = 0; i < m_original_lines.size(); ++i) 141 dbgln("{}:{}", i, m_original_lines[i]); 142 } 143} 144 145DiffViewer::DiffViewer() 146{ 147 setup_properties(); 148} 149 150DiffViewer::DiffViewer(DeprecatedString const& original, DeprecatedString const& diff) 151 : m_original_lines(split_to_lines(original)) 152 , m_hunks(Diff::parse_hunks(diff)) 153{ 154 setup_properties(); 155} 156 157void DiffViewer::setup_properties() 158{ 159 set_font(Gfx::FontDatabase::default_fixed_width_font()); 160 set_background_role(ColorRole::Base); 161 set_foreground_role(ColorRole::BaseText); 162} 163 164Vector<DeprecatedString> DiffViewer::split_to_lines(DeprecatedString const& text) 165{ 166 // NOTE: This is slightly different than text.split('\n') 167 Vector<DeprecatedString> lines; 168 size_t next_line_start_index = 0; 169 for (size_t i = 0; i < text.length(); ++i) { 170 if (text[i] == '\n') { 171 auto line_text = text.substring(next_line_start_index, i - next_line_start_index); 172 lines.append(move(line_text)); 173 next_line_start_index = i + 1; 174 } 175 } 176 lines.append(text.substring(next_line_start_index, text.length() - next_line_start_index)); 177 return lines; 178} 179 180Gfx::Color DiffViewer::red_background() 181{ 182 static Gfx::Color color = Gfx::Color::from_argb(0x88ff0000); 183 return color; 184} 185 186Gfx::Color DiffViewer::green_background() 187{ 188 static Gfx::Color color = Gfx::Color::from_argb(0x8800ff00); 189 return color; 190} 191 192Gfx::Color DiffViewer::gray_background() 193{ 194 static Gfx::Color color = Gfx::Color::from_argb(0x88888888); 195 return color; 196} 197 198void DiffViewer::update_content_size() 199{ 200 if (m_hunks.is_empty()) { 201 set_content_size({ 0, 0 }); 202 return; 203 } 204 205 size_t num_lines = 0; 206 size_t current_original_line_index = 0; 207 for (auto const& hunk : m_hunks) { 208 num_lines += ((int)hunk.original_start_line - (int)current_original_line_index); 209 210 num_lines += hunk.removed_lines.size(); 211 if (hunk.added_lines.size() > hunk.removed_lines.size()) { 212 num_lines += ((int)hunk.added_lines.size() - (int)hunk.removed_lines.size()); 213 } 214 current_original_line_index = hunk.original_start_line + hunk.removed_lines.size(); 215 } 216 num_lines += ((int)m_original_lines.size() - (int)current_original_line_index); 217 218 // TODO: Support Horizontal scrolling 219 set_content_size({ 0, (int)(num_lines * line_height()) }); 220} 221 222void DiffViewer::resize_event(GUI::ResizeEvent& event) 223{ 224 AbstractScrollableWidget::resize_event(event); 225 update_content_size(); 226} 227 228}