| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| 11 | |
| 12 | #include <cmath> |
| 13 | |
| 14 | #include "imgui_internal.h" |
| 15 | |
| 16 | #include "dtl.h" |
| 17 | #include "TextDiff.h" |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 24 | TextDiff::TextDiff() { |
| 25 | readOnly = true; |
| 26 | showLineNumbers = false; |
| 27 | showMatchingBrackets = false; |
| 28 | } |
| 29 | |
| 30 | |
| 31 | |
| 32 | |
| 33 | |
| 34 | |
| 35 | void TextDiff::SetText(const std::string_view& left, const std::string_view& right) { |
| 36 | |
| 37 | std::vector<std::string_view> leftLines; |
| 38 | std::vector<std::string_view> rightLines; |
| 39 | splitLines(leftLines, left); |
| 40 | splitLines(rightLines, right); |
| 41 | |
| 42 | |
| 43 | leftDocument.setText(leftLines); |
| 44 | rightDocument.setText(rightLines); |
| 45 | |
| 46 | colorizer.updateEntireDocument(leftDocument, language); |
| 47 | colorizer.updateEntireDocument(rightDocument, language); |
| 48 | |
| 49 | |
| 50 | leftLineNumberDigits = static_cast<int>(std::log10(leftDocument.lineCount() + 1) + 1.0f); |
| 51 | rightLineNumberDigits = static_cast<int>(std::log10(leftDocument.lineCount() + 1) + 1.0f); |
| 52 | decoratorWidth = -(leftLineNumberDigits + rightLineNumberDigits + 4.0f); |
| 53 | |
| 54 | decoratorCallback = [this](TextEditor::Decorator& decorator) { |
| 55 | this->decorateLine(decorator); |
| 56 | }; |
| 57 | |
| 58 | |
| 59 | dtl::Diff<std::string_view> diff(leftLines, rightLines); |
| 60 | diff.compose(); |
| 61 | auto ranges = diff.getSes().getSequence(); |
| 62 | |
| 63 | |
| 64 | lineInfo.clear(); |
| 65 | size_t leftIndex = 0; |
| 66 | size_t rightIndex = 0; |
| 67 | |
| 68 | for (auto& [text, info] : ranges) { |
| 69 | switch (info.type) { |
| 70 | case dtl::SES_COMMON: |
| 71 | lineInfo.emplace_back(leftIndex++, rightIndex++, LineStatus::common); |
| 72 | break; |
| 73 | |
| 74 | case dtl::SES_ADD: |
| 75 | lineInfo.emplace_back(leftIndex, rightIndex++, LineStatus::added); |
| 76 | break; |
| 77 | |
| 78 | case dtl::SES_DELETE: |
| 79 | lineInfo.emplace_back(leftIndex++, rightIndex, LineStatus::deleted); |
| 80 | break; |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | |
| 85 | updated = true; |
| 86 | } |
| 87 | |
| 88 | |
| 89 | |
| 90 | |
| 91 | |
| 92 | |
| 93 | void TextDiff::SetLanguage(const Language* l) { |
| 94 | language = l; |
| 95 | colorizer.updateEntireDocument(leftDocument, language); |
| 96 | colorizer.updateEntireDocument(rightDocument, language); |
| 97 | updated = true; |
| 98 | } |
| 99 | |
| 100 | |
| 101 | |
| 102 | |
| 103 | |
| 104 | |
| 105 | void TextDiff::Render(const char* title, const ImVec2& size, bool border) { |
| 106 | if (sideBySideMode) { |
| 107 | |
| 108 | renderSideBySide(title, size, border); |
| 109 | |
| 110 | } else { |
| 111 | |
| 112 | if (updated) { |
| 113 | createCombinedView(); |
| 114 | updated = false; |
| 115 | } |
| 116 | |
| 117 | |
| 118 | TextEditor::render(title, size, border); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | |
| 123 | |
| 124 | |
| 125 | |
| 126 | |
| 127 | void TextDiff::splitLines(std::vector<std::string_view>& result, const std::string_view& text) { |
| 128 | size_t prev = CodePoint::skipBOM(text.begin(), text.end()) - text.begin(); |
| 129 | size_t pos; |
| 130 | |
| 131 | while ((pos = text.find('\n', prev)) != std::string_view::npos) { |
| 132 | result.push_back(text.substr(prev, pos - prev)); |
| 133 | prev = pos + 1; |
| 134 | } |
| 135 | |
| 136 | result.push_back(text.substr(prev)); |
| 137 | } |
| 138 | |
| 139 | |
| 140 | |
| 141 | |
| 142 | |
| 143 | |
| 144 | void TextDiff::createCombinedView() { |
| 145 | document.clear(); |
| 146 | cursors.clearAll(); |
| 147 | clearMarkers(); |
| 148 | |
| 149 | for (size_t i = 0; i < lineInfo.size(); i++) { |
| 150 | auto& line = lineInfo[i]; |
| 151 | |
| 152 | switch(line.status) { |
| 153 | case LineStatus::common: |
| 154 | document.emplace_back(leftDocument[line.leftLine]); |
| 155 | break; |
| 156 | |
| 157 | case LineStatus::added: |
| 158 | document.emplace_back(rightDocument[line.rightLine]); |
| 159 | addMarker(static_cast<int>(i), 0, addedColor, "", ""); |
| 160 | break; |
| 161 | |
| 162 | case LineStatus::deleted: |
| 163 | document.emplace_back(leftDocument[line.leftLine]); |
| 164 | addMarker(static_cast<int>(i), 0, deletedColor, "", ""); |
| 165 | break; |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | document.updateMaximumColumn(0, document.lineCount() - 1); |
| 170 | } |
| 171 | |
| 172 | |
| 173 | |
| 174 | |
| 175 | |
| 176 | |
| 177 | void TextDiff::decorateLine(TextEditor::Decorator& decorator) { |
| 178 | auto& line = lineInfo[decorator.line]; |
| 179 | auto lineLeft = static_cast<int>(line.leftLine + 1); |
| 180 | auto lineRight = static_cast<int>(line.rightLine + 1); |
| 181 | auto color = ImGui::ColorConvertU32ToFloat4(palette.get(Color::lineNumber)); |
| 182 | |
| 183 | switch(line.status) { |
| 184 | case LineStatus::common: |
| 185 | ImGui::TextColored(color, " %*d %*d ", leftLineNumberDigits, lineLeft, rightLineNumberDigits, lineRight); |
| 186 | break; |
| 187 | |
| 188 | case LineStatus::added: |
| 189 | ImGui::TextColored(color, " %*s %*d +", leftLineNumberDigits, "", rightLineNumberDigits, lineRight); |
| 190 | break; |
| 191 | |
| 192 | case LineStatus::deleted: |
| 193 | ImGui::TextColored(color, " %*d %*s -", leftLineNumberDigits, lineLeft, rightLineNumberDigits, ""); |
| 194 | break; |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | |
| 199 | |
| 200 | |
| 201 | |
| 202 | |
| 203 | void TextDiff::renderSideBySide(const char* title, const ImVec2& size, bool border) { |
| 204 | |
| 205 | auto& style = ImGui::GetStyle(); |
| 206 | |
| 207 | if (paletteAlpha != style.Alpha) { |
| 208 | updatePalette(); |
| 209 | } |
| 210 | |
| 211 | |
| 212 | font = ImGui::GetFont(); |
| 213 | fontSize = ImGui::GetFontSize(); |
| 214 | glyphSize = ImVec2(ImGui::CalcTextSize("#").x, ImGui::GetTextLineHeightWithSpacing() * lineSpacing); |
| 215 | |
| 216 | |
| 217 | if (scrollToLineNumber >= 0) { |
| 218 | scrollToLineNumber = std::min(scrollToLineNumber, static_cast<int>(lineInfo.size())); |
| 219 | |
| 220 | switch (scrollToAlignment) { |
| 221 | case Scroll::alignTop: |
| 222 | ImGui::SetNextWindowScroll(ImVec2(0.0f, std::max(0.0f, static_cast<float>(scrollToLineNumber) * glyphSize.y))); |
| 223 | break; |
| 224 | |
| 225 | case Scroll::alignMiddle: |
| 226 | ImGui::SetNextWindowScroll(ImVec2(0.0f, std::max(0.0f, static_cast<float>(scrollToLineNumber - visibleLines / 2) * glyphSize.y))); |
| 227 | break; |
| 228 | |
| 229 | case Scroll::alignBottom: |
| 230 | ImGui::SetNextWindowScroll(ImVec2(0.0f, std::max(0.0f, static_cast<float>(scrollToLineNumber - (visibleLines - 1)) * glyphSize.y))); |
| 231 | break; |
| 232 | } |
| 233 | |
| 234 | scrollToLineNumber = -1; |
| 235 | } |
| 236 | |
| 237 | |
| 238 | if (focusOnEditor) { |
| 239 | ImGui::SetNextWindowFocus(); |
| 240 | focusOnEditor = false; |
| 241 | } |
| 242 | |
| 243 | |
| 244 | ImGui::SetNextWindowContentSize(ImVec2(0.0f, glyphSize.y * lineInfo.size())); |
| 245 | ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); |
| 246 | ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(palette.get(Color::background))); |
| 247 | ImGui::BeginChild(title, size, border, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoNavInputs); |
| 248 | |
| 249 | auto cursorScreenPos = ImGui::GetCursorScreenPos(); |
| 250 | auto visibleSize = ImGui::GetCurrentWindow()->Rect().GetSize(); |
| 251 | |
| 252 | |
| 253 | leftLineNumberWidth = glyphSize.x * (leftLineNumberDigits + 4); |
| 254 | rightLineNumberWidth = glyphSize.x * (rightLineNumberDigits + 4); |
| 255 | textColumnWidth = static_cast<float>(visibleSize.x - leftLineNumberWidth - rightLineNumberWidth) / 2.0f; |
| 256 | |
| 257 | leftLineNumberPos = cursorScreenPos.x; |
| 258 | leftTextPos = leftLineNumberPos + leftLineNumberWidth; |
| 259 | rightLineNumberPos = leftTextPos + textColumnWidth; |
| 260 | rightTextPos = rightLineNumberPos + rightLineNumberWidth; |
| 261 | rightTextEnd = rightTextPos + textColumnWidth; |
| 262 | |
| 263 | visibleLines = std::max(static_cast<int>(std::ceil(visibleSize.y / glyphSize.y)), 0); |
| 264 | visibleColumns = std::max(static_cast<int>(std::ceil(textColumnWidth / glyphSize.x)), 0); |
| 265 | |
| 266 | firstVisibleColumn = std::max(static_cast<int>(std::floor(textScroll / glyphSize.x)), 0); |
| 267 | lastVisibleColumn = static_cast<int>(std::floor((textScroll + textColumnWidth) / glyphSize.x)); |
| 268 | firstVisibleLine = std::max(static_cast<int>(std::floor(ImGui::GetScrollY() / glyphSize.y)), 0); |
| 269 | lastVisibleLine = std::min(static_cast<int>(std::floor((ImGui::GetScrollY() + visibleSize.y) / glyphSize.y)), static_cast<int>(lineInfo.size() - 1)); |
| 270 | |
| 271 | renderSideBySideBackground(); |
| 272 | renderSideBySideText(); |
| 273 | renderSideBySideTextScrollbars(); |
| 274 | renderSideBySideMiniMap(); |
| 275 | |
| 276 | ImGui::EndChild(); |
| 277 | ImGui::PopStyleColor(); |
| 278 | ImGui::PopStyleVar(); |
| 279 | } |
| 280 | |
| 281 | |
| 282 | |
| 283 | |
| 284 | |
| 285 | |
| 286 | void TextDiff::renderSideBySideBackground() { |
| 287 | |
| 288 | auto drawList = ImGui::GetWindowDrawList(); |
| 289 | auto y = ImGui::GetCursorScreenPos().y + firstVisibleLine * glyphSize.y; |
| 290 | char buffer[32]; |
| 291 | |
| 292 | for (auto i = firstVisibleLine; i <= lastVisibleLine; i++) { |
| 293 | auto& line = lineInfo[i]; |
| 294 | auto lineLeft = static_cast<int>(line.leftLine + 1); |
| 295 | auto lineRight = static_cast<int>(line.rightLine + 1); |
| 296 | |
| 297 | switch(line.status) { |
| 298 | case LineStatus::common: |
| 299 | snprintf(buffer, sizeof(buffer), " %*d", leftLineNumberDigits, lineLeft); |
| 300 | drawList->AddText(ImVec2(leftLineNumberPos, y), palette.get(Color::lineNumber), buffer); |
| 301 | snprintf(buffer, sizeof(buffer), " %*d", rightLineNumberDigits, lineRight); |
| 302 | drawList->AddText(ImVec2(rightLineNumberPos, y), palette.get(Color::lineNumber), buffer); |
| 303 | break; |
| 304 | |
| 305 | case LineStatus::added: |
| 306 | snprintf(buffer, sizeof(buffer), " %*d +", rightLineNumberDigits, lineRight); |
| 307 | drawList->AddText(ImVec2(rightLineNumberPos, y), palette.get(Color::lineNumber), buffer); |
| 308 | |
| 309 | drawList->AddRectFilled( |
| 310 | ImVec2(rightTextPos, y), |
| 311 | ImVec2(rightTextEnd, y + glyphSize.y), |
| 312 | addedColor); |
| 313 | |
| 314 | break; |
| 315 | |
| 316 | case LineStatus::deleted: |
| 317 | snprintf(buffer, sizeof(buffer), " %*d -", leftLineNumberDigits, lineLeft); |
| 318 | drawList->AddText(ImVec2(leftLineNumberPos, y), palette.get(Color::lineNumber), buffer); |
| 319 | |
| 320 | drawList->AddRectFilled( |
| 321 | ImVec2(leftTextPos, y), |
| 322 | ImVec2(rightLineNumberPos, y + glyphSize.y), |
| 323 | deletedColor); |
| 324 | |
| 325 | break; |
| 326 | } |
| 327 | |
| 328 | y += glyphSize.y; |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | |
| 333 | |
| 334 | |
| 335 | |
| 336 | |
| 337 | void TextDiff::renderSideBySideText() { |
| 338 | |
| 339 | auto drawList = ImGui::GetWindowDrawList(); |
| 340 | auto cursorScreenPos = ImGui::GetCursorScreenPos(); |
| 341 | auto yTop = drawList->GetClipRectMin().y; |
| 342 | auto yBottom = drawList->GetClipRectMax().y; |
| 343 | |
| 344 | |
| 345 | drawList->PushClipRect(ImVec2(leftTextPos, yTop), ImVec2(rightLineNumberPos, yBottom), false); |
| 346 | |
| 347 | for (auto i = firstVisibleLine; i <= lastVisibleLine; i++) { |
| 348 | auto& line = lineInfo[i]; |
| 349 | auto y = cursorScreenPos.y + i * glyphSize.y; |
| 350 | |
| 351 | switch(line.status) { |
| 352 | case LineStatus::common: |
| 353 | case LineStatus::deleted: |
| 354 | renderSideBySideLine(leftTextPos, y, leftDocument[lineInfo[i].leftLine]); |
| 355 | break; |
| 356 | |
| 357 | case LineStatus::added: |
| 358 | break; |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | drawList->PopClipRect(); |
| 363 | |
| 364 | |
| 365 | drawList->PushClipRect(ImVec2(rightTextPos, yTop), ImVec2(rightTextEnd, yBottom), false); |
| 366 | |
| 367 | for (auto i = firstVisibleLine; i <= lastVisibleLine; i++) { |
| 368 | auto& line = lineInfo[i]; |
| 369 | auto y = cursorScreenPos.y + i * glyphSize.y; |
| 370 | |
| 371 | switch(line.status) { |
| 372 | case LineStatus::common: |
| 373 | case LineStatus::added: |
| 374 | renderSideBySideLine(rightTextPos, y, rightDocument[lineInfo[i].rightLine]); |
| 375 | break; |
| 376 | |
| 377 | case LineStatus::deleted: |
| 378 | break; |
| 379 | } |
| 380 | } |
| 381 | |
| 382 | drawList->PopClipRect(); |
| 383 | } |
| 384 | |
| 385 | |
| 386 | |
| 387 | |
| 388 | |
| 389 | |
| 390 | void TextDiff::renderSideBySideLine(float x, float y, TextEditor::Line& line) { |
| 391 | |
| 392 | auto drawList = ImGui::GetWindowDrawList(); |
| 393 | auto tabSize = document.getTabSize(); |
| 394 | auto firstRenderableColumn = (firstVisibleColumn / tabSize) * tabSize; |
| 395 | |
| 396 | auto column = firstRenderableColumn; |
| 397 | auto index = document.getIndex(line, column); |
| 398 | auto lineSize = line.size(); |
| 399 | |
| 400 | while (index < lineSize && column <= lastVisibleColumn) { |
| 401 | auto& glyph = line[index]; |
| 402 | auto codepoint = glyph.codepoint; |
| 403 | ImVec2 glyphPos(x + column * glyphSize.x - textScroll, y); |
| 404 | |
| 405 | if (codepoint == '\t') { |
| 406 | if (showTabs) { |
| 407 | const auto x1 = glyphPos.x + glyphSize.x * 0.3f; |
| 408 | const auto y1 = glyphPos.y + fontSize * 0.5f; |
| 409 | const auto x2 = glyphPos.x + glyphSize.x; |
| 410 | |
| 411 | ImVec2 p1, p2, p3, p4; |
| 412 | p1 = ImVec2(x1, y1); |
| 413 | p2 = ImVec2(x2, y1); |
| 414 | p3 = ImVec2(x2 - fontSize * 0.16f, y1 - fontSize * 0.16f); |
| 415 | p4 = ImVec2(x2 - fontSize * 0.16f, y1 + fontSize * 0.16f); |
| 416 | |
| 417 | drawList->AddLine(p1, p2, palette.get(Color::whitespace)); |
| 418 | drawList->AddLine(p2, p3, palette.get(Color::whitespace)); |
| 419 | drawList->AddLine(p2, p4, palette.get(Color::whitespace)); |
| 420 | } |
| 421 | |
| 422 | } else if (codepoint == ' ') { |
| 423 | if (showSpaces) { |
| 424 | const auto x1 = glyphPos.x + glyphSize.x * 0.5f; |
| 425 | const auto y1 = glyphPos.y + fontSize * 0.5f; |
| 426 | drawList->AddCircleFilled(ImVec2(x1, y1), 1.5f, palette.get(Color::whitespace), 4); |
| 427 | } |
| 428 | |
| 429 | } else { |
| 430 | font->RenderChar(drawList, fontSize, glyphPos, palette.get(glyph.color), codepoint); |
| 431 | } |
| 432 | |
| 433 | index++; |
| 434 | column += (codepoint == '\t') ? tabSize - (column % tabSize) : 1; |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | |
| 439 | |
| 440 | |
| 441 | |
| 442 | |
| 443 | void TextDiff::renderSideBySideTextScrollbars() { |
| 444 | auto maxColumnsWidth = std::max(leftDocument.getMaxColumn(), rightDocument.getMaxColumn()) * glyphSize.x; |
| 445 | auto visibleColumnsWidth = rightLineNumberPos - leftTextPos; |
| 446 | |
| 447 | if (maxColumnsWidth > visibleColumnsWidth) { |
| 448 | auto window = ImGui::GetCurrentWindow(); |
| 449 | const ImRect outerRect = window->Rect(); |
| 450 | auto borderSize = std::round(window->WindowBorderSize * 0.5f); |
| 451 | auto scrollbarSize = ImGui::GetStyle().ScrollbarSize; |
| 452 | |
| 453 | auto textScrollbarTop = std::max(outerRect.Min.y + borderSize, outerRect.Max.y - borderSize - scrollbarSize); |
| 454 | ImRect leftScrollbarFrame(leftTextPos, textScrollbarTop, rightLineNumberPos, textScrollbarTop + scrollbarSize); |
| 455 | ImRect rightScrollbarFrame(rightTextPos, textScrollbarTop, rightTextEnd, textScrollbarTop + scrollbarSize); |
| 456 | ImS64 scroll = static_cast<ImS64>(textScroll); |
| 457 | |
| 458 | if (ImGui::ScrollbarEx( |
| 459 | leftScrollbarFrame, |
| 460 | ImGui::GetID("leftTextScroll"), |
| 461 | ImGuiAxis_X, |
| 462 | &scroll, |
| 463 | static_cast<ImS64>(visibleColumnsWidth), |
| 464 | static_cast<ImS64>(maxColumnsWidth), |
| 465 | ImDrawFlags_RoundCornersAll)) { textScroll = static_cast<float>(scroll); } |
| 466 | |
| 467 | if (ImGui::ScrollbarEx( |
| 468 | rightScrollbarFrame, |
| 469 | ImGui::GetID("rightTextScroll"), |
| 470 | ImGuiAxis_X, |
| 471 | &scroll, |
| 472 | static_cast<ImS64>(visibleColumnsWidth), |
| 473 | static_cast<ImS64>(maxColumnsWidth), |
| 474 | ImDrawFlags_RoundCornersAll)) { textScroll = static_cast<float>(scroll); } |
| 475 | |
| 476 | if (ImGui::IsWindowHovered()) { |
| 477 | textScroll = std::clamp(textScroll - ImGui::GetIO().MouseWheelH * ImGui::GetFontSize(), 0.0f, maxColumnsWidth - visibleColumnsWidth); |
| 478 | |
| 479 | if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) { |
| 480 | textScroll = std::max(textScroll - glyphSize.x, 0.0f); |
| 481 | |
| 482 | } else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) { |
| 483 | textScroll = std::min(textScroll + glyphSize.x, maxColumnsWidth - visibleColumnsWidth); |
| 484 | |
| 485 | } else if (ImGui::IsKeyPressed(ImGuiKey_Home)) { |
| 486 | textScroll = 0.0f; |
| 487 | |
| 488 | } else if (ImGui::IsKeyPressed(ImGuiKey_End)) { |
| 489 | textScroll = maxColumnsWidth - visibleColumnsWidth; |
| 490 | } |
| 491 | } |
| 492 | } |
| 493 | |
| 494 | if (ImGui::IsWindowHovered()) { |
| 495 | if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) { |
| 496 | if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) { |
| 497 | ImGui::SetScrollY(0.0f); |
| 498 | |
| 499 | } else { |
| 500 | ImGui::SetScrollY(std::max(ImGui::GetScrollY() - glyphSize.y, 0.0f)); |
| 501 | } |
| 502 | |
| 503 | } else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) { |
| 504 | if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) { |
| 505 | ImGui::SetScrollY(ImGui::GetScrollMaxY()); |
| 506 | |
| 507 | } else { |
| 508 | ImGui::SetScrollY(std::min(ImGui::GetScrollY() + glyphSize.y, ImGui::GetScrollMaxY())); |
| 509 | } |
| 510 | |
| 511 | } else if (ImGui::IsKeyPressed(ImGuiKey_PageUp)) { |
| 512 | ImGui::SetScrollY(std::max(ImGui::GetScrollY() - (visibleLines - 2) * glyphSize.y, 0.0f)); |
| 513 | |
| 514 | } else if (ImGui::IsKeyPressed(ImGuiKey_PageDown)) { |
| 515 | ImGui::SetScrollY(std::min(ImGui::GetScrollY() + (visibleLines - 2) * glyphSize.y, ImGui::GetScrollMaxY())); |
| 516 | } |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | |
| 521 | |
| 522 | |
| 523 | |
| 524 | |
| 525 | void TextDiff::renderSideBySideMiniMap() { |
| 526 | |
| 527 | if (showScrollbarMiniMap) { |
| 528 | auto window = ImGui::GetCurrentWindow(); |
| 529 | |
| 530 | if (window->ScrollbarY) { |
| 531 | auto drawList = ImGui::GetWindowDrawList(); |
| 532 | auto rect = ImGui::GetWindowScrollbarRect(window, ImGuiAxis_Y); |
| 533 | auto lineHeight = rect.GetHeight() / static_cast<float>(document.size()); |
| 534 | auto offset = (rect.Max.x - rect.Min.x) * 0.3f; |
| 535 | auto left = rect.Min.x + offset; |
| 536 | auto right = rect.Max.x - offset; |
| 537 | |
| 538 | drawList->PushClipRect(rect.Min, rect.Max, false); |
| 539 | |
| 540 | |
| 541 | for (size_t i = 0; i < lineInfo.size(); i++) { |
| 542 | auto& line = lineInfo[i]; |
| 543 | |
| 544 | if (line.status != LineStatus::common) { |
| 545 | auto color = (line.status == LineStatus::added) ? addedColor : deletedColor; |
| 546 | auto ly = std::round(rect.Min.y + i * lineHeight); |
| 547 | drawList->AddRectFilled(ImVec2(left, ly), ImVec2(right, ly + lineHeight), color); |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | drawList->PopClipRect(); |
| 552 | } |
| 553 | } |
| 554 | } |