Plan document open actions in app core

This commit is contained in:
2026-06-02 23:06:36 +02:00
parent 1df506a176
commit fd1772a417
9 changed files with 179 additions and 12 deletions

View File

@@ -394,6 +394,9 @@ Known local toolchain state:
- `pano_cli classify-open` exposes the `pp_app_core` document-open route - `pano_cli classify-open` exposes the `pp_app_core` document-open route
contract as JSON and is covered for project files, ABR imports, PPBR contract as JSON and is covered for project files, ABR imports, PPBR
imports, and malformed path rejection. imports, and malformed path rejection.
- `pano_cli plan-open-route` exposes `pp_app_core` document-open action
planning as JSON and is covered for clean project open, dirty project
discard-prompt, and ABR import-prompt states.
- `pano_cli plan-document-file` exposes `pp_app_core` document-name - `pano_cli plan-document-file` exposes `pp_app_core` document-name
validation, legacy `.ppi` path construction, and overwrite-prompt decisions validation, legacy `.ppi` path construction, and overwrite-prompt decisions
as JSON and is covered for save-now and existing-target overwrite states. as JSON and is covered for save-now and existing-target overwrite states.
@@ -417,9 +420,9 @@ Known local toolchain state:
directory/stem targets, picked-directory stems, MP4 suggested names, and directory/stem targets, picked-directory stems, MP4 suggested names, and
invalid export naming inputs. invalid export naming inputs.
- `pp_app_core_document_session_tests` covers clean and dirty app session, - `pp_app_core_document_session_tests` covers clean and dirty app session,
save-request, save-before-workflow, document file target, overwrite, and document-open action planning, save-request, save-before-workflow, document
save-version target decisions without requiring a window, canvas, or message file target, overwrite, and save-version target decisions without requiring
box. a window, canvas, or message box.
- `pp_ui_core` consumes vcpkg tinyxml2 only when `PP_USE_VCPKG_TINYXML2=ON` - `pp_ui_core` consumes vcpkg tinyxml2 only when `PP_USE_VCPKG_TINYXML2=ON`
through the vcpkg preset; default and Android validation still use the through the vcpkg preset; default and Android validation still use the
retained vendored fallback tracked by DEBT-0012. retained vendored fallback tracked by DEBT-0012.

View File

@@ -12,7 +12,7 @@ and validation command.
| Capability | Current Area | Target Owner | Required Tests | | Capability | Current Area | Target Owner | Required Tests |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| PPI open/save | `Canvas`, serializer, dialogs | `pp_document`, `pp_assets`, `pano_cli` | Round-trip tiny project, old-version fixture, corrupt/truncated fixture | | PPI open/save | `Canvas`, serializer, dialogs | `pp_document`, `pp_assets`, `pano_cli` | Round-trip tiny project, old-version fixture, corrupt/truncated fixture |
| Open-document routing | `App::open_document` | `pp_app_core`, `pano_cli`, `pp_panopainter_ui`, `pp_document`, `pp_assets` | Project/ABR/PPBR route tests, malformed path tests, CLI route smoke, app open smoke | | Open-document routing | `App::open_document` | `pp_app_core`, `pano_cli`, `pp_panopainter_ui`, `pp_document`, `pp_assets` | Project/ABR/PPBR route tests, malformed path tests, open-action plan tests, CLI route/action smoke, app open smoke |
| Document session decisions | `App::open_document`, `App::request_close`, save hotkeys, file menu, dialogs | `pp_app_core`, `pano_cli`, `pp_panopainter_ui` | Clean/dirty/prompt-open/save/save-as/save-version/save-before-workflow/name/overwrite/version-target decision tests, CLI session, document-file, and document-version smoke, app close/open/save/new/browse smoke | | Document session decisions | `App::open_document`, `App::request_close`, save hotkeys, file menu, dialogs | `pp_app_core`, `pano_cli`, `pp_panopainter_ui` | Clean/dirty/prompt-open/save/save-as/save-version/save-before-workflow/name/overwrite/version-target decision tests, CLI session, document-file, and document-version smoke, app close/open/save/new/browse smoke |
| Version metadata | `scripts/pre-build.py`, `version.*` | build system, `pp_foundation` | Generated header smoke test, missing-tag behavior | | Version metadata | `scripts/pre-build.py`, `version.*` | build system, `pp_foundation` | Generated header smoke test, missing-tag behavior |
| Thumbnail generation/read | `Canvas`, `Image` | `pp_assets`, `pp_paint_renderer` | Golden thumbnail, corrupt input | | Thumbnail generation/read | `Canvas`, `Image` | `pp_assets`, `pp_paint_renderer` | Golden thumbnail, corrupt input |

View File

@@ -22,7 +22,7 @@ agent or engineer to remove them without reconstructing context from chat.
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
| DEBT-0001 | Open | Modernization | Existing platform build files remain alongside new CMake | Required for incremental migration without losing platform coverage | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets | | DEBT-0001 | Open | Modernization | Existing platform build files remain alongside new CMake | Required for incremental migration without losing platform coverage | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets |
| DEBT-0002 | Open | Modernization | Vendored SDK and patched libraries retained initially | Some dependencies are SDK-only, patched, or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace with vcpkg packages or document permanent vendored status after triplet evaluation | | DEBT-0002 | Open | Modernization | Vendored SDK and patched libraries retained initially | Some dependencies are SDK-only, patched, or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace with vcpkg packages or document permanent vendored status after triplet evaluation |
| DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, file-menu save actions, `NodeCanvas` save hotkeys, new/open/browse dirty-document workflow prompts, new/save-as document file naming and overwrite decisions, save-version target decisions, export target naming/path decisions, `pano_cli classify-open`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-export-target`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session/export contracts, but document loading, saving, and export execution still reach legacy `Canvas::I` and UI singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_session_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries | | DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, file-menu save actions, `NodeCanvas` save hotkeys, new/open/browse dirty-document workflow prompts, new/save-as document file naming and overwrite decisions, save-version target decisions, export target naming/path decisions, `pano_cli classify-open`, `pano_cli plan-open-route`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-export-target`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session/export contracts, but document loading, brush import execution, saving, and export execution still reach legacy `Canvas::I`/UI singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_session_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-open-route --path D:/Paint/demo.ppi --unsaved`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries |
| DEBT-0004 | Open | Modernization | Android, Linux, WebGL, Apple, and AppX build files remain platform-specific until root CMake alignment reaches them | Prevent platform regressions during incremental migration; raw Windows `.sln/.vcxproj` files were removed on 2026-05-31 by user decision | `cmake --preset windows-msvc-default`; platform-specific configure/build smoke checks as each platform is migrated | Root CMake owns every platform source list and package path | | DEBT-0004 | Open | Modernization | Android, Linux, WebGL, Apple, and AppX build files remain platform-specific until root CMake alignment reaches them | Prevent platform regressions during incremental migration; raw Windows `.sln/.vcxproj` files were removed on 2026-05-31 by user decision | `cmake --preset windows-msvc-default`; platform-specific configure/build smoke checks as each platform is migrated | Root CMake owns every platform source list and package path |
| DEBT-0005 | Open | Modernization | Temporary local CTest harness is used before Catch2 is wired through vcpkg | `vcpkg` is not currently on PATH, but headless tests need to run now | `ctest --preset desktop-fast --build-config Debug` | Replace `tests/test_harness.h` tests with Catch2 tests once vcpkg toolchain/presets are validated | | DEBT-0005 | Open | Modernization | Temporary local CTest harness is used before Catch2 is wired through vcpkg | `vcpkg` is not currently on PATH, but headless tests need to run now | `ctest --preset desktop-fast --build-config Debug` | Replace `tests/test_harness.h` tests with Catch2 tests once vcpkg toolchain/presets are validated |
| DEBT-0007 | Open | Modernization | `vcpkg.json` and `windows-msvc-vcpkg-headless` are validated for the headless Windows component matrix, but app targets still use vendored libraries and Android/Apple triplets are not proven | Dependency migration must stay incremental while SDK/patched/vendor dependencies remain in use | `$env:VCPKG_ROOT="C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg"; cmake --preset windows-msvc-vcpkg-headless`; `ctest --preset desktop-fast-vcpkg --build-config Debug` | Component targets consume vcpkg packages where reliable and desktop app, Android, and Apple triplets are validated or explicitly documented as permanent vendor exceptions | | DEBT-0007 | Open | Modernization | `vcpkg.json` and `windows-msvc-vcpkg-headless` are validated for the headless Windows component matrix, but app targets still use vendored libraries and Android/Apple triplets are not proven | Dependency migration must stay incremental while SDK/patched/vendor dependencies remain in use | `$env:VCPKG_ROOT="C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg"; cmake --preset windows-msvc-vcpkg-headless`; `ctest --preset desktop-fast-vcpkg --build-config Debug` | Component targets consume vcpkg packages where reliable and desktop app, Android, and Apple triplets are validated or explicitly documented as permanent vendor exceptions |

View File

@@ -423,9 +423,11 @@ the applied stroke payload survives inspect/load round-trip automation, with a
rejection smoke test for unsafe tiny canvas dimensions. rejection smoke test for unsafe tiny canvas dimensions.
`pano_cli classify-open` exposes the pure `pp_app_core` document-open route `pano_cli classify-open` exposes the pure `pp_app_core` document-open route
contract for project files, ABR imports, PPBR imports, and malformed path contract for project files, ABR imports, PPBR imports, and malformed path
rejection. `pano_cli simulate-app-session` exposes the pure `pp_app_core` rejection. `pano_cli plan-open-route` exposes the pure `pp_app_core`
session decisions used by project-open, app-close, save, save-as, and document-open action plan for clean project open, dirty project prompt, and
save-version flows, plus the save-before-continue workflow gate used by brush-import prompt flows. `pano_cli simulate-app-session` exposes the pure
`pp_app_core` session decisions used by project-open, app-close, save, save-as,
and save-version flows, plus the save-before-continue workflow gate used by
new-document/open/browse dialogs. `pano_cli plan-document-file` exposes the new-document/open/browse dialogs. `pano_cli plan-document-file` exposes the
same app-core document-name validation, legacy `.ppi` path construction, and same app-core document-name validation, legacy `.ppi` path construction, and
overwrite prompt decision used by new-document and save-as dialogs. overwrite prompt decision used by new-document and save-as dialogs.

View File

@@ -193,7 +193,10 @@ void App::open_document(std::string path)
if (!route) if (!route)
return; return;
if (route.value().kind == pp::app::DocumentOpenKind::import_abr) const bool has_unsaved_project =
route.value().kind == pp::app::DocumentOpenKind::open_project && Canvas::I->m_unsaved;
const auto open_plan = pp::app::plan_document_open(route.value().kind, has_unsaved_project);
if (open_plan == pp::app::DocumentOpenPlanAction::prompt_import_abr)
{ {
auto mb = message_box("Import ABR", "Would you like to import the brushes?", true); auto mb = message_box("Import ABR", "Would you like to import the brushes?", true);
mb->on_submit = [this, path] (Node* target) { mb->on_submit = [this, path] (Node* target) {
@@ -201,7 +204,7 @@ void App::open_document(std::string path)
target->destroy(); target->destroy();
}; };
} }
else if (route.value().kind == pp::app::DocumentOpenKind::import_ppbr) else if (open_plan == pp::app::DocumentOpenPlanAction::prompt_import_ppbr)
{ {
auto mb = message_box("Import PPBR", "Would you like to import the brushes?", true); auto mb = message_box("Import PPBR", "Would you like to import the brushes?", true);
mb->on_submit = [this, path] (Node* target) { mb->on_submit = [this, path] (Node* target) {
@@ -239,8 +242,7 @@ void App::open_document(std::string path)
}); });
ActionManager::clear(); ActionManager::clear();
}; };
const auto open_decision = pp::app::plan_project_open(Canvas::I->m_unsaved); if (open_plan == pp::app::DocumentOpenPlanAction::open_project_now)
if (open_decision == pp::app::ProjectOpenDecision::open_now)
{ {
open_action(); open_action();
} }

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "app_core/document_route.h"
#include "foundation/result.h" #include "foundation/result.h"
#include <cctype> #include <cctype>
@@ -46,6 +47,13 @@ enum class DocumentFileWriteDecision {
prompt_overwrite, prompt_overwrite,
}; };
enum class DocumentOpenPlanAction {
open_project_now,
prompt_discard_unsaved_project,
prompt_import_abr,
prompt_import_ppbr,
};
struct DocumentFileTarget { struct DocumentFileTarget {
std::string name; std::string name;
std::string directory; std::string directory;
@@ -64,6 +72,24 @@ struct DocumentVersionTarget {
: ProjectOpenDecision::open_now; : ProjectOpenDecision::open_now;
} }
[[nodiscard]] constexpr DocumentOpenPlanAction plan_document_open(
DocumentOpenKind kind,
bool has_unsaved_changes) noexcept
{
switch (kind) {
case DocumentOpenKind::import_abr:
return DocumentOpenPlanAction::prompt_import_abr;
case DocumentOpenKind::import_ppbr:
return DocumentOpenPlanAction::prompt_import_ppbr;
case DocumentOpenKind::open_project:
return has_unsaved_changes
? DocumentOpenPlanAction::prompt_discard_unsaved_project
: DocumentOpenPlanAction::open_project_now;
}
return DocumentOpenPlanAction::open_project_now;
}
[[nodiscard]] constexpr CloseRequestDecision plan_close_request( [[nodiscard]] constexpr CloseRequestDecision plan_close_request(
bool has_unsaved_changes, bool has_unsaved_changes,
bool close_prompt_already_open) noexcept bool close_prompt_already_open) noexcept

View File

@@ -381,6 +381,24 @@ if(TARGET pano_cli)
LABELS "app;integration;desktop-fast;fuzz" LABELS "app;integration;desktop-fast;fuzz"
) )
add_test(NAME pano_cli_plan_open_route_project_clean_smoke
COMMAND pano_cli plan-open-route --path "D:/Paint/Scenes/demo.ppi")
set_tests_properties(pano_cli_plan_open_route_project_clean_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-open-route\".*\"kind\":\"open-project\".*\"unsaved\":false.*\"action\":\"open-project-now\"")
add_test(NAME pano_cli_plan_open_route_project_unsaved_smoke
COMMAND pano_cli plan-open-route --path "D:/Paint/Scenes/demo.ppi" --unsaved)
set_tests_properties(pano_cli_plan_open_route_project_unsaved_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-open-route\".*\"kind\":\"open-project\".*\"unsaved\":true.*\"action\":\"prompt-discard-unsaved-project\"")
add_test(NAME pano_cli_plan_open_route_abr_import_smoke
COMMAND pano_cli plan-open-route --path "D:/Paint/Brushes/clouds.ABR" --unsaved)
set_tests_properties(pano_cli_plan_open_route_abr_import_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-open-route\".*\"kind\":\"import-abr\".*\"unsaved\":true.*\"action\":\"prompt-import-abr\"")
add_test(NAME pano_cli_plan_document_file_save_now_smoke add_test(NAME pano_cli_plan_document_file_save_now_smoke
COMMAND pano_cli plan-document-file --work-dir D:/Paint --name demo) COMMAND pano_cli plan-document-file --work-dir D:/Paint --name demo)
set_tests_properties(pano_cli_plan_document_file_save_now_smoke PROPERTIES set_tests_properties(pano_cli_plan_document_file_save_now_smoke PROPERTIES

View File

@@ -15,6 +15,38 @@ void project_open_dirty_document_prompts_for_discard(pp::tests::Harness& harness
pp::app::plan_project_open(true) == pp::app::ProjectOpenDecision::prompt_discard_unsaved); pp::app::plan_project_open(true) == pp::app::ProjectOpenDecision::prompt_discard_unsaved);
} }
void document_open_project_respects_unsaved_state(pp::tests::Harness& harness)
{
PP_EXPECT(
harness,
pp::app::plan_document_open(pp::app::DocumentOpenKind::open_project, false)
== pp::app::DocumentOpenPlanAction::open_project_now);
PP_EXPECT(
harness,
pp::app::plan_document_open(pp::app::DocumentOpenKind::open_project, true)
== pp::app::DocumentOpenPlanAction::prompt_discard_unsaved_project);
}
void document_open_brush_imports_prompt_regardless_of_unsaved_state(pp::tests::Harness& harness)
{
PP_EXPECT(
harness,
pp::app::plan_document_open(pp::app::DocumentOpenKind::import_abr, false)
== pp::app::DocumentOpenPlanAction::prompt_import_abr);
PP_EXPECT(
harness,
pp::app::plan_document_open(pp::app::DocumentOpenKind::import_abr, true)
== pp::app::DocumentOpenPlanAction::prompt_import_abr);
PP_EXPECT(
harness,
pp::app::plan_document_open(pp::app::DocumentOpenKind::import_ppbr, false)
== pp::app::DocumentOpenPlanAction::prompt_import_ppbr);
PP_EXPECT(
harness,
pp::app::plan_document_open(pp::app::DocumentOpenKind::import_ppbr, true)
== pp::app::DocumentOpenPlanAction::prompt_import_ppbr);
}
void close_clean_document_executes_immediately(pp::tests::Harness& harness) void close_clean_document_executes_immediately(pp::tests::Harness& harness)
{ {
PP_EXPECT( PP_EXPECT(
@@ -206,6 +238,10 @@ int main()
pp::tests::Harness harness; pp::tests::Harness harness;
harness.run("project open clean document executes immediately", project_open_clean_document_executes_immediately); harness.run("project open clean document executes immediately", project_open_clean_document_executes_immediately);
harness.run("project open dirty document prompts for discard", project_open_dirty_document_prompts_for_discard); harness.run("project open dirty document prompts for discard", project_open_dirty_document_prompts_for_discard);
harness.run("document open project respects unsaved state", document_open_project_respects_unsaved_state);
harness.run(
"document open brush imports prompt regardless of unsaved state",
document_open_brush_imports_prompt_regardless_of_unsaved_state);
harness.run("close clean document executes immediately", close_clean_document_executes_immediately); harness.run("close clean document executes immediately", close_clean_document_executes_immediately);
harness.run("close dirty document opens one prompt", close_dirty_document_opens_one_prompt); harness.run("close dirty document opens one prompt", close_dirty_document_opens_one_prompt);
harness.run("save clean existing document is no op", save_clean_existing_document_is_no_op); harness.run("save clean existing document is no op", save_clean_existing_document_is_no_op);

View File

@@ -97,6 +97,11 @@ struct ClassifyOpenArgs {
std::string path; std::string path;
}; };
struct PlanOpenRouteArgs {
std::string path;
bool unsaved = false;
};
struct PlanDocumentFileArgs { struct PlanDocumentFileArgs {
std::string work_directory; std::string work_directory;
std::string name; std::string name;
@@ -275,6 +280,22 @@ const char* project_open_decision_name(pp::app::ProjectOpenDecision decision) no
return "open-now"; return "open-now";
} }
const char* document_open_plan_action_name(pp::app::DocumentOpenPlanAction action) noexcept
{
switch (action) {
case pp::app::DocumentOpenPlanAction::open_project_now:
return "open-project-now";
case pp::app::DocumentOpenPlanAction::prompt_discard_unsaved_project:
return "prompt-discard-unsaved-project";
case pp::app::DocumentOpenPlanAction::prompt_import_abr:
return "prompt-import-abr";
case pp::app::DocumentOpenPlanAction::prompt_import_ppbr:
return "prompt-import-ppbr";
}
return "open-project-now";
}
const char* close_request_decision_name(pp::app::CloseRequestDecision decision) noexcept const char* close_request_decision_name(pp::app::CloseRequestDecision decision) noexcept
{ {
switch (decision) { switch (decision) {
@@ -377,6 +398,7 @@ void print_help()
<< " import-image --path FILE [--document-width N] [--document-height N] [--face N] [--x N] [--y N]\n" << " import-image --path FILE [--document-width N] [--document-height N] [--face N] [--x N] [--y N]\n"
<< " inspect-project --path FILE\n" << " inspect-project --path FILE\n"
<< " classify-open --path FILE\n" << " classify-open --path FILE\n"
<< " plan-open-route --path FILE [--unsaved]\n"
<< " plan-document-file --work-dir DIR --name NAME [--target-exists]\n" << " plan-document-file --work-dir DIR --name NAME [--target-exists]\n"
<< " plan-document-version --directory DIR --doc-name NAME [--existing-path FILE]\n" << " plan-document-version --directory DIR --doc-name NAME [--existing-path FILE]\n"
<< " plan-export-target --kind file|collection|stem|name --doc-name NAME [--work-dir DIR] [--directory DIR] [--extension EXT] [--suffix SUFFIX]\n" << " plan-export-target --kind file|collection|stem|name --doc-name NAME [--work-dir DIR] [--directory DIR] [--extension EXT] [--suffix SUFFIX]\n"
@@ -1221,6 +1243,60 @@ int classify_open(int argc, char** argv)
return 0; return 0;
} }
pp::foundation::Status parse_plan_open_route_args(
int argc,
char** argv,
PlanOpenRouteArgs& args)
{
for (int i = 2; i < argc; ++i) {
const std::string_view key(argv[i]);
if (key == "--path") {
if (i + 1 >= argc) {
return pp::foundation::Status::invalid_argument("missing value for option");
}
args.path = argv[++i];
} else if (key == "--unsaved") {
args.unsaved = true;
} else {
return pp::foundation::Status::invalid_argument("unknown option");
}
}
if (args.path.empty()) {
return pp::foundation::Status::invalid_argument("path must not be empty");
}
return pp::foundation::Status::success();
}
int plan_open_route(int argc, char** argv)
{
PlanOpenRouteArgs args;
const auto status = parse_plan_open_route_args(argc, argv, args);
if (!status.ok()) {
print_error("plan-open-route", status.message);
return 2;
}
const auto route = pp::app::classify_document_open_path(args.path);
if (!route) {
print_error("plan-open-route", route.status().message);
return 2;
}
const auto action = pp::app::plan_document_open(route.value().kind, args.unsaved);
std::cout << "{\"ok\":true,\"command\":\"plan-open-route\""
<< ",\"route\":{\"kind\":\"" << document_open_kind_name(route.value().kind)
<< "\",\"path\":\"" << json_escape(route.value().path)
<< "\",\"directory\":\"" << json_escape(route.value().directory)
<< "\",\"name\":\"" << json_escape(route.value().name)
<< "\",\"extension\":\"" << json_escape(route.value().extension)
<< "\"},\"state\":{\"unsaved\":" << json_bool(args.unsaved)
<< "},\"plan\":{\"action\":\"" << document_open_plan_action_name(action)
<< "\"}}\n";
return 0;
}
pp::foundation::Status parse_plan_document_file_args( pp::foundation::Status parse_plan_document_file_args(
int argc, int argc,
char** argv, char** argv,
@@ -3475,6 +3551,10 @@ int main(int argc, char** argv)
return classify_open(argc, argv); return classify_open(argc, argv);
} }
if (command == "plan-open-route") {
return plan_open_route(argc, argv);
}
if (command == "plan-document-file") { if (command == "plan-document-file") {
return plan_document_file(argc, argv); return plan_document_file(argc, argv);
} }