1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 12:48:10 +00:00

LibGUI: Add MoveLineUpOrDownCommand

This allows lines moved by Ctrl+Shift+[Up, Down] to be registered as a
command, i.e. cancellable by Ctrl+Z.

This patch also introduces the usage of TextDocument::[take,
insert]_line. Those functions forward changes to the visual lines and
then avoid some data mismatch.

Co-authored-by: Jorropo <jorropo.pgm@gmail.com>
This commit is contained in:
Lucas CHOLLET 2022-06-07 22:32:35 +02:00 committed by Linus Groh
parent cf693136e2
commit 7a8104e79b
3 changed files with 145 additions and 55 deletions

View file

@ -259,8 +259,11 @@ EditingEngine::DidMoveALine EditingEngine::move_one_up(KeyEvent const& event)
{
if (m_editor->cursor().line() > 0 || m_editor->is_wrapping_enabled()) {
if (event.ctrl() && event.shift()) {
move_selected_lines_up();
return DidMoveALine::Yes;
if (MoveLineUpOrDownCommand::valid_operation(*this, VerticalDirection::Up)) {
m_editor->execute<MoveLineUpOrDownCommand>(Badge<EditingEngine> {}, event, *this);
return DidMoveALine::Yes;
}
return DidMoveALine::No;
}
TextPosition new_cursor;
if (m_editor->is_wrapping_enabled()) {
@ -280,8 +283,11 @@ EditingEngine::DidMoveALine EditingEngine::move_one_down(KeyEvent const& event)
{
if (m_editor->cursor().line() < (m_editor->line_count() - 1) || m_editor->is_wrapping_enabled()) {
if (event.ctrl() && event.shift()) {
move_selected_lines_down();
return DidMoveALine::Yes;
if (MoveLineUpOrDownCommand::valid_operation(*this, VerticalDirection::Down)) {
m_editor->execute<MoveLineUpOrDownCommand>(Badge<EditingEngine> {}, event, *this);
return DidMoveALine::Yes;
}
return DidMoveALine::No;
}
TextPosition new_cursor;
if (m_editor->is_wrapping_enabled()) {
@ -353,6 +359,11 @@ void EditingEngine::move_to_last_line()
m_editor->set_cursor(m_editor->line_count() - 1, m_editor->lines()[m_editor->line_count() - 1].length());
};
void EditingEngine::get_selection_line_boundaries(Badge<MoveLineUpOrDownCommand>, size_t& first_line, size_t& last_line)
{
get_selection_line_boundaries(first_line, last_line);
}
void EditingEngine::get_selection_line_boundaries(size_t& first_line, size_t& last_line)
{
auto selection = m_editor->normalized_selection();
@ -367,55 +378,6 @@ void EditingEngine::get_selection_line_boundaries(size_t& first_line, size_t& la
last_line -= 1;
}
void EditingEngine::move_selected_lines_up()
{
if (!m_editor->is_editable())
return;
size_t first_line;
size_t last_line;
get_selection_line_boundaries(first_line, last_line);
if (first_line == 0)
return;
auto& lines = m_editor->document().lines();
lines.insert((int)last_line, lines.take((int)first_line - 1));
m_editor->set_cursor({ m_editor->cursor().line() - 1, m_editor->cursor().column() });
if (m_editor->has_selection()) {
m_editor->selection().start().set_line(m_editor->selection().start().line() - 1);
m_editor->selection().end().set_line(m_editor->selection().end().line() - 1);
}
m_editor->did_change();
m_editor->update();
}
void EditingEngine::move_selected_lines_down()
{
if (!m_editor->is_editable())
return;
size_t first_line;
size_t last_line;
get_selection_line_boundaries(first_line, last_line);
auto& lines = m_editor->document().lines();
VERIFY(lines.size() != 0);
if (last_line >= lines.size() - 1)
return;
lines.insert((int)first_line, lines.take((int)last_line + 1));
m_editor->set_cursor({ m_editor->cursor().line() + 1, m_editor->cursor().column() });
if (m_editor->has_selection()) {
m_editor->selection().start().set_line(m_editor->selection().start().line() + 1);
m_editor->selection().end().set_line(m_editor->selection().end().line() + 1);
}
m_editor->did_change();
m_editor->update();
}
void EditingEngine::delete_char()
{
if (!m_editor->is_editable())
@ -430,4 +392,105 @@ void EditingEngine::delete_line()
m_editor->delete_current_line();
};
MoveLineUpOrDownCommand::MoveLineUpOrDownCommand(TextDocument& document, KeyEvent event, EditingEngine& engine)
: TextDocumentUndoCommand(document)
, m_event(move(event))
, m_direction(key_code_to_vertical_direction(m_event.key()))
, m_engine(engine)
, m_selection(m_engine.editor().selection())
, m_cursor(m_engine.editor().cursor())
{
}
void MoveLineUpOrDownCommand::redo()
{
move_lines(m_direction);
}
void MoveLineUpOrDownCommand::undo()
{
move_lines(!m_direction);
}
bool MoveLineUpOrDownCommand::merge_with(GUI::Command const&)
{
return false;
}
String MoveLineUpOrDownCommand::action_text() const
{
return "Move a line";
}
bool MoveLineUpOrDownCommand::valid_operation(EditingEngine& engine, VerticalDirection direction)
{
VERIFY(engine.editor().line_count() != 0);
auto const& selection = engine.editor().selection().normalized();
if (selection.is_valid()) {
if ((direction == VerticalDirection::Up && selection.start().line() == 0) || (direction == VerticalDirection::Down && selection.end().line() >= engine.editor().line_count() - 1))
return false;
} else {
size_t first_line;
size_t last_line;
engine.get_selection_line_boundaries(Badge<MoveLineUpOrDownCommand> {}, first_line, last_line);
if ((direction == VerticalDirection::Up && first_line == 0) || (direction == VerticalDirection::Down && last_line >= engine.editor().line_count() - 1))
return false;
}
return true;
}
TextRange MoveLineUpOrDownCommand::retrieve_selection(VerticalDirection direction)
{
if (direction == m_direction)
return m_selection;
auto const offset_selection = [this](auto const offset) {
auto tmp = m_selection;
tmp.start().set_line(tmp.start().line() + offset);
tmp.end().set_line(tmp.end().line() + offset);
return tmp;
};
if (direction == VerticalDirection::Up)
return offset_selection(1);
if (direction == VerticalDirection::Down)
return offset_selection(-1);
VERIFY_NOT_REACHED();
}
void MoveLineUpOrDownCommand::move_lines(VerticalDirection direction)
{
if (m_event.shift() && m_selection.is_valid()) {
m_engine.editor().set_selection(retrieve_selection(direction));
m_engine.editor().did_update_selection();
}
if (!m_engine.editor().is_editable())
return;
size_t first_line;
size_t last_line;
m_engine.get_selection_line_boundaries(Badge<MoveLineUpOrDownCommand> {}, first_line, last_line);
auto const offset = direction == VerticalDirection::Up ? -1 : 1;
auto const insertion_index = direction == VerticalDirection::Up ? last_line : first_line;
auto const moved_line_index = offset + (direction != VerticalDirection::Up ? last_line : first_line);
auto moved_line = m_document.take_line(moved_line_index);
m_document.insert_line(insertion_index, move(moved_line));
m_engine.editor().set_cursor({ m_engine.editor().cursor().line() + offset, m_engine.editor().cursor().column() });
if (m_engine.editor().has_selection()) {
m_engine.editor().selection().start().set_line(m_engine.editor().selection().start().line() + offset);
m_engine.editor().selection().end().set_line(m_engine.editor().selection().end().line() + offset);
}
m_engine.editor().did_change();
m_engine.editor().update();
}
}