8d81497eb7
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Add a new action that can contain multiple other actions. <!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> ## References <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [x] Closes #3992 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx * [x] Schema updated. * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx <!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments Creates a shortcut action that allows a list of actions to be specified as arguments. Steals a bunch of the serialization code from my other pr. Overall, because I had the serialization code written already, this was remarkably easy. I can't think of any combined action to be added to the defaults, so I think this is just a thing for the documentation unless someone else has a good example. I know there are lot of times when the recommended workaround is "make an action with commandline wt.exe ..." and this could be a good replacement for that, but that is all personalized. I didn't add this to the command line parsing, since the command line is already a way to run multiple actions. <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed Created a new command, confirmed that "Move right->down" showed up in the command palette, and that running it did the correct behavior (moving right one pane, then down one pane). ``` { "command": { "action": "multipleActions", "name": "Move right->down", "actions": [ {"action": "moveFocus", "direction": "right" }, {"action": "moveFocus", "direction": "down" }, ] } } ```
174 lines
6.3 KiB
C++
174 lines
6.3 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "ApplicationState.h"
|
|
#include "CascadiaSettings.h"
|
|
#include "ApplicationState.g.cpp"
|
|
|
|
#include "JsonUtils.h"
|
|
#include "FileUtils.h"
|
|
|
|
constexpr std::wstring_view stateFileName{ L"state.json" };
|
|
|
|
namespace Microsoft::Terminal::Settings::Model::JsonUtils
|
|
{
|
|
// This trait exists in order to serialize the std::unordered_set for GeneratedProfiles.
|
|
template<typename T>
|
|
struct ConversionTrait<std::unordered_set<T>>
|
|
{
|
|
std::unordered_set<T> FromJson(const Json::Value& json) const
|
|
{
|
|
ConversionTrait<T> trait;
|
|
std::unordered_set<T> val;
|
|
val.reserve(json.size());
|
|
|
|
for (const auto& element : json)
|
|
{
|
|
val.emplace(trait.FromJson(element));
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
bool CanConvert(const Json::Value& json) const
|
|
{
|
|
ConversionTrait<T> trait;
|
|
return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) -> bool { return trait.CanConvert(json); });
|
|
}
|
|
|
|
Json::Value ToJson(const std::unordered_set<T>& val)
|
|
{
|
|
ConversionTrait<T> trait;
|
|
Json::Value json{ Json::arrayValue };
|
|
|
|
for (const auto& key : val)
|
|
{
|
|
json.append(trait.ToJson(key));
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
std::string TypeDescription() const
|
|
{
|
|
return fmt::format("{}[]", ConversionTrait<GUID>{}.TypeDescription());
|
|
}
|
|
};
|
|
}
|
|
|
|
using namespace ::Microsoft::Terminal::Settings::Model;
|
|
|
|
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|
{
|
|
// Returns the application-global ApplicationState object.
|
|
Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance()
|
|
{
|
|
static auto state = winrt::make_self<ApplicationState>(GetBaseSettingsPath() / stateFileName);
|
|
return *state;
|
|
}
|
|
|
|
ApplicationState::ApplicationState(std::filesystem::path path) noexcept :
|
|
_path{ std::move(path) },
|
|
_throttler{ std::chrono::seconds(1), [this]() { _write(); } }
|
|
{
|
|
_read();
|
|
}
|
|
|
|
// The destructor ensures that the last write is flushed to disk before returning.
|
|
ApplicationState::~ApplicationState()
|
|
{
|
|
// This will ensure that we not just cancel the last outstanding timer,
|
|
// but instead force it to run as soon as possible and wait for it to complete.
|
|
_throttler.flush();
|
|
}
|
|
|
|
// Re-read the state.json from disk.
|
|
void ApplicationState::Reload() const noexcept
|
|
{
|
|
_read();
|
|
}
|
|
|
|
// Returns the state.json path on the disk.
|
|
winrt::hstring ApplicationState::FilePath() const noexcept
|
|
{
|
|
return winrt::hstring{ _path.wstring() };
|
|
}
|
|
|
|
// Generate all getter/setters
|
|
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
|
type ApplicationState::name() const noexcept \
|
|
{ \
|
|
const auto state = _state.lock_shared(); \
|
|
const auto& value = state->name; \
|
|
return value ? *value : type{ __VA_ARGS__ }; \
|
|
} \
|
|
\
|
|
void ApplicationState::name(const type& value) noexcept \
|
|
{ \
|
|
{ \
|
|
auto state = _state.lock(); \
|
|
state->name.emplace(value); \
|
|
} \
|
|
\
|
|
_throttler(); \
|
|
}
|
|
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
|
#undef MTSM_APPLICATION_STATE_GEN
|
|
|
|
// Deserializes the state.json at _path into this ApplicationState.
|
|
// * ANY errors during app state will result in the creation of a new empty state.
|
|
// * ANY errors during runtime will result in changes being partially ignored.
|
|
void ApplicationState::_read() const noexcept
|
|
try
|
|
{
|
|
const auto data = ReadUTF8FileIfExists(_path).value_or(std::string{});
|
|
if (data.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::string errs;
|
|
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
|
|
|
Json::Value root;
|
|
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
|
|
{
|
|
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
|
}
|
|
|
|
auto state = _state.lock();
|
|
// GetValueForKey() comes in two variants:
|
|
// * take a std::optional<T> reference
|
|
// * return std::optional<T> by value
|
|
// At the time of writing the former version skips missing fields in the json,
|
|
// but we want to explicitly clear state fields that were removed from state.json.
|
|
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key);
|
|
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
|
#undef MTSM_APPLICATION_STATE_GEN
|
|
}
|
|
CATCH_LOG()
|
|
|
|
// Serialized this ApplicationState (in `context`) into the state.json at _path.
|
|
// * Errors are only logged.
|
|
// * _state->_writeScheduled is set to false, signaling our
|
|
// setters that _synchronize() needs to be called again.
|
|
void ApplicationState::_write() const noexcept
|
|
try
|
|
{
|
|
Json::Value root{ Json::objectValue };
|
|
|
|
{
|
|
auto state = _state.lock_shared();
|
|
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name);
|
|
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
|
#undef MTSM_APPLICATION_STATE_GEN
|
|
}
|
|
|
|
Json::StreamWriterBuilder wbuilder;
|
|
const auto content = Json::writeString(wbuilder, root);
|
|
WriteUTF8FileAtomic(_path, content);
|
|
}
|
|
CATCH_LOG()
|
|
}
|