Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Fix command history cycling with DOWN ARROW key #18140

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
23 changes: 20 additions & 3 deletions src/host/history.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "precomp.h"

#include "history.h"
Expand Down Expand Up @@ -849,3 +846,23 @@ HRESULT ApiRoutines::GetConsoleCommandHistoryWImpl(const std::wstring_view exeNa
}
CATCH_RETURN();
}

std::wstring_view CommandHistory::CycleCommandHistory(const SearchDirection searchDirection)
{
if (_commands.empty())
{
LastDisplayed = 0;
return {};
}

if (searchDirection == SearchDirection::Previous)
{
LastDisplayed = (LastDisplayed - 1 + GetNumberOfCommands()) % GetNumberOfCommands();
}
else
{
LastDisplayed = (LastDisplayed + 1) % GetNumberOfCommands();
}

return _commands.at(LastDisplayed);
}
191 changes: 95 additions & 96 deletions src/host/history.h
Original file line number Diff line number Diff line change
@@ -1,96 +1,95 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#pragma once

class CommandHistory
{
public:
using Index = int32_t;
static constexpr Index IndexMax = INT32_MAX;

// CommandHistory Flags
static constexpr int CLE_ALLOCATED = 0x00000001;
static constexpr int CLE_RESET = 0x00000002;

static CommandHistory* s_Allocate(const std::wstring_view appName, const HANDLE processHandle);
static CommandHistory* s_Find(const HANDLE processHandle);
static CommandHistory* s_FindByExe(const std::wstring_view appName);
static void s_ReallocExeToFront(const std::wstring_view appName, const size_t commands);
static void s_Free(const HANDLE processHandle);
static void s_ResizeAll(const size_t commands);
static size_t s_CountOfHistories();

enum class MatchOptions
{
None = 0x0,
ExactMatch = 0x1,
JustLooking = 0x2
};

enum class SearchDirection
{
Previous,
Next
};

bool FindMatchingCommand(const std::wstring_view command,
const Index startingIndex,
Index& indexFound,
const MatchOptions options);
bool IsAppNameMatch(const std::wstring_view other) const;

[[nodiscard]] HRESULT Add(const std::wstring_view command,
const bool suppressDuplicates);

std::wstring_view Retrieve(const SearchDirection searchDirection);
std::wstring_view RetrieveNth(Index index);

Index GetNumberOfCommands() const;
std::wstring_view GetNth(Index index) const;
const std::vector<std::wstring>& GetCommands() const noexcept;

void Realloc(Index commands);
void Empty();

std::wstring Remove(const Index iDel);

bool AtFirstCommand() const;
bool AtLastCommand() const;

std::wstring_view GetLastCommand() const;

void Swap(const Index indexA, const Index indexB);

private:
void _Reset();

// _Next and _Prev go to the next and prev command
// _Inc and _Dec go to the next and prev slots
// Don't get the two confused - it matters when the cmd history is not full!
void _Prev(Index& ind) const;
void _Next(Index& ind) const;
void _Dec(Index& ind) const;
void _Inc(Index& ind) const;

// NOTE: In conhost v1 this used to be a circular buffer because removal at the
// start is a very common operation. It seems this was lost in the C++ refactor.
std::vector<std::wstring> _commands;
Index _maxCommands = 0;

std::wstring _appName;
HANDLE _processHandle = nullptr;

static std::list<CommandHistory> s_historyLists;

public:
DWORD Flags = 0;
Index LastDisplayed = 0;

#ifdef UNIT_TESTING
static void s_ClearHistoryListStorage();
friend class HistoryTests;
#endif
};

DEFINE_ENUM_FLAG_OPERATORS(CommandHistory::MatchOptions);
#pragma once

class CommandHistory
{
public:
using Index = int32_t;
static constexpr Index IndexMax = INT32_MAX;

// CommandHistory Flags
static constexpr int CLE_ALLOCATED = 0x00000001;
static constexpr int CLE_RESET = 0x00000002;

static CommandHistory* s_Allocate(const std::wstring_view appName, const HANDLE processHandle);
static CommandHistory* s_Find(const HANDLE processHandle);
static CommandHistory* s_FindByExe(const std::wstring_view appName);
static void s_ReallocExeToFront(const std::wstring_view appName, const size_t commands);
static void s_Free(const HANDLE processHandle);
static void s_ResizeAll(const size_t commands);
static size_t s_CountOfHistories();

enum class MatchOptions
{
None = 0x0,
ExactMatch = 0x1,
JustLooking = 0x2
};

enum class SearchDirection
{
Previous,
Next
};

bool FindMatchingCommand(const std::wstring_view command,
const Index startingIndex,
Index& indexFound,
const MatchOptions options);
bool IsAppNameMatch(const std::wstring_view other) const;

[[nodiscard]] HRESULT Add(const std::wstring_view command,
const bool suppressDuplicates);

std::wstring_view Retrieve(const SearchDirection searchDirection);
std::wstring_view RetrieveNth(Index index);

Index GetNumberOfCommands() const;
std::wstring_view GetNth(Index index) const;
const std::vector<std::wstring>& GetCommands() const noexcept;

void Realloc(Index commands);
void Empty();

std::wstring Remove(const Index iDel);

bool AtFirstCommand() const;
bool AtLastCommand() const;

std::wstring_view GetLastCommand() const;

void Swap(const Index indexA, const Index indexB);

std::wstring_view CycleCommandHistory(const SearchDirection searchDirection);

private:
void _Reset();

// _Next and _Prev go to the next and prev command
// _Inc and _Dec go to the next and prev slots
// Don't get the two confused - it matters when the cmd history is not full!
void _Prev(Index& ind) const;
void _Next(Index& ind) const;
void _Dec(Index& ind) const;
void _Inc(Index& ind) const;

// NOTE: In conhost v1 this used to be a circular buffer because removal at the
// start is a very common operation. It seems this was lost in the C++ refactor.
std::vector<std::wstring> _commands;
Index _maxCommands = 0;

std::wstring _appName;
HANDLE _processHandle = nullptr;

static std::list<CommandHistory> s_historyLists;

public:
DWORD Flags = 0;
Index LastDisplayed = 0;

#ifdef UNIT_TESTING
static void s_ClearHistoryListStorage();
friend class HistoryTests;
#endif
};

DEFINE_ENUM_FLAG_OPERATORS(CommandHistory::MatchOptions);
Loading