Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Separate namespace for ANSI code sequences to avoid magic strings
  • Loading branch information
ianthomas23 committed Oct 10, 2025
commit afd276a2ed07453069bec1c1d6329c3631a096b7
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ set(GIT2CPP_SRC
${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/utils/ansi_code.cpp
${GIT2CPP_SOURCE_DIR}/utils/ansi_code.hpp
${GIT2CPP_SOURCE_DIR}/utils/common.cpp
${GIT2CPP_SOURCE_DIR}/utils/common.hpp
${GIT2CPP_SOURCE_DIR}/utils/git_exception.cpp
Expand Down
24 changes: 24 additions & 0 deletions src/utils/ansi_code.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include "ansi_code.hpp"

namespace ansi_code
{
std::string cursor_to_row(size_t row)
{
return "\e[" + std::to_string(row) + "H";
}

bool is_down_arrow(std::string str)
{
return str == "\e[B" || str == "\e[1B]";
}

bool is_escape_char(char ch)
{
return ch == '\e';
}

bool is_up_arrow(std::string str)
{
return str == "\e[A" || str == "\e[1A]";
}
}
29 changes: 29 additions & 0 deletions src/utils/ansi_code.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include <string>

/**
* ANSI escape codes.
* Use `termcolor` for colours.
*/
namespace ansi_code
{
// Constants.
const std::string bel = "\a"; // ASCII 7, used for audio/visual feedback.
const std::string cursor_to_top = "\e[H";
const std::string erase_screen = "\e[2J";

const std::string enable_alternative_buffer = "\e[?1049h";
const std::string disable_alternative_buffer = "\e[?1049l";

const std::string hide_cursor = "\e[?25l";
const std::string show_cursor = "\e[?25h";

// Functions.
std::string cursor_to_row(size_t row);

bool is_escape_char(char ch);

bool is_down_arrow(std::string str);
bool is_up_arrow(std::string str);
}
5 changes: 3 additions & 2 deletions src/utils/output.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <iostream>
#include "ansi_code.hpp"
#include "common.hpp"

// Scope object to hide the cursor. This avoids
Expand All @@ -10,11 +11,11 @@ struct cursor_hider : noncopyable_nonmovable
{
cursor_hider()
{
std::cout << "\e[?25l";
std::cout << ansi_code::hide_cursor;
}

~cursor_hider()
{
std::cout << "\e[?25h";
std::cout << ansi_code::show_cursor;
}
};
22 changes: 11 additions & 11 deletions src/utils/terminal_pager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <termcolor/termcolor.hpp>

#include "ansi_code.hpp"
#include "terminal_pager.hpp"

terminal_pager::terminal_pager()
Expand All @@ -31,7 +32,7 @@ std::string terminal_pager::get_input() const
std::cin.get(ch);
str += ch;

if (ch == '\e') // Start of ANSI escape sequence.
if (ansi_code::is_escape_char(ch)) // Start of ANSI escape sequence.
{
do
{
Expand Down Expand Up @@ -83,19 +84,19 @@ bool terminal_pager::process_input(std::string input)
return false;
case '\e': // ANSI escape sequence.
// Cannot switch on a std::string.
if (input == "\e[A" || input == "\e[1A]") // Up arrow.
if (ansi_code::is_up_arrow(input))
{
scroll(true, false); // Up a line.
return false;
}
else if (input == "\e[B" || input == "\e[1B]") // Down arrow.
else if (ansi_code::is_down_arrow(input))
{
scroll(false, false); // Down a line.
return false;
}
}

std::cout << '\a'; // Emit BEL for visual feedback.
std::cout << ansi_code::bel;
return false;
}

Expand All @@ -108,8 +109,8 @@ void terminal_pager::render_terminal() const
{
auto end_row_index = m_start_row_index + m_rows - 1;

std::cout << "\e[2J"; // Erase screen.
std::cout << "\e[H"; // Cursor to top.
std::cout << ansi_code::erase_screen;
std::cout << ansi_code::cursor_to_top;

for (size_t i = m_start_row_index; i < end_row_index; i++)
{
Expand All @@ -120,7 +121,7 @@ void terminal_pager::render_terminal() const
std::cout << m_lines[i] << std::endl;
}

std::cout << "\e[" << m_rows << "H"; // Move cursor to bottom row of terminal.
std::cout << ansi_code::cursor_to_row(m_rows); // Move cursor to bottom row of terminal.
std::cout << ":";
}

Expand Down Expand Up @@ -154,8 +155,7 @@ void terminal_pager::scroll(bool up, bool page)

if (m_start_row_index == old_start_row_index)
{
// No change, emit BEL for visual feedback.
std::cout << '\a';
std::cout << ansi_code::bel;
}
else
{
Expand Down Expand Up @@ -188,7 +188,7 @@ void terminal_pager::show()
new_termios.c_lflag &= (~ICANON & ~ECHO);
tcsetattr(fileno(stdin), TCSANOW, &new_termios);
Comment thread
JohanMabille marked this conversation as resolved.
Outdated

std::cout << "\e[?1049h"; // Enable alternative buffer.
std::cout << ansi_code::enable_alternative_buffer;

m_start_row_index = 0;
render_terminal();
Expand All @@ -199,7 +199,7 @@ void terminal_pager::show()
stop = process_input(get_input());
} while (!stop);

std::cout << "\e[?1049l"; // Disable alternative buffer.
std::cout << ansi_code::disable_alternative_buffer;

// Restore original termios settings.
tcsetattr(fileno(stdin), TCSANOW, &old_termios);
Expand Down