Skip to content

Extension tests, add custom formats with saving and loading (#4950)#5174

Merged
dacap merged 1 commit into
aseprite:betafrom
ckaiser:omelette-du-format
Mar 20, 2026
Merged

Extension tests, add custom formats with saving and loading (#4950)#5174
dacap merged 1 commit into
aseprite:betafrom
ckaiser:omelette-du-format

Conversation

@ckaiser

@ckaiser ckaiser commented May 20, 2025

Copy link
Copy Markdown
Member

Implements #4950.

Test extension:
ppm-format.zip

Comment thread src/app/commands/cmd_open_file.cpp Outdated
Comment thread src/app/commands/cmd_save_file.cpp Outdated
Comment thread src/app/commands/cmd_save_file.h Outdated
Comment thread src/app/extensions.cpp Outdated
Comment thread src/app/extensions.cpp Outdated
Comment thread src/app/extensions.h Outdated
Comment thread src/app/extensions.h Outdated
Comment thread src/app/script/plugin_class.cpp Outdated
Comment thread src/app/script/plugin_class.cpp Outdated
Comment thread src/app/script/sprite_class.cpp Outdated
@dacap dacap assigned ckaiser and unassigned dacap Jul 1, 2025
@ckaiser ckaiser force-pushed the omelette-du-format branch from 6f4300c to a4f69bd Compare July 15, 2025 17:45

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/app/commands/cmd_open_file.cpp Outdated
Comment thread src/app/commands/cmd_save_file.cpp Outdated
Comment thread src/app/commands/cmd_save_file.cpp Outdated
Comment thread src/app/extensions.cpp Outdated
Comment thread src/app/extensions.cpp Outdated
Comment thread src/app/extensions.cpp Outdated
Comment thread src/app/script/plugin_class.cpp
Comment thread src/app/script/sprite_class.cpp Outdated
@ckaiser ckaiser force-pushed the omelette-du-format branch from a4f69bd to 7d99ade Compare July 22, 2025 06:11
@ckaiser

ckaiser commented Jul 22, 2025

Copy link
Copy Markdown
Member Author

Had another go at this and implementing the suggestions, it's a bit cleaner now, still have some TODOs to look over though but I wanted to get feedback on the save protection stuff and the general idea.

One thing that's nagging at me right now as well is that as it stands now, the script itself is in charge of creating the Sprite itself, and that seems like something we might want to avoid. Originally I left it like that because with adding isModified and having the script have to set the filename, we could leave it open for scripts to open multiple sprites from a single file, but if that's not behavior we intend to support, then I think creating the sprite behind the scenes and then passing it through an argument to the script so it can manipulate the pixels and then we can mark it as saved/etc is gonna be the way to go.

Trying to think if there's any use cases that this way of doing things might make impossible though.

@ckaiser ckaiser assigned dacap and unassigned ckaiser Jul 22, 2025
@ckaiser ckaiser force-pushed the omelette-du-format branch from 7d99ade to 67f383f Compare December 5, 2025 10:19
@ZeWaka

ZeWaka commented Dec 12, 2025

Copy link
Copy Markdown

Perhaps the new File Explorer -> File Associations menu added in #2606 is relevant? An API to add into there would be wonderful.

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/app/extensions.cpp
Comment thread src/app/extensions.cpp
Comment thread src/app/extensions.cpp
Comment thread src/app/extensions.cpp
Comment thread src/app/extensions.cpp
Comment thread src/app/extensions_tests.cpp
Comment thread src/app/extensions_tests.cpp
Comment thread src/app/extensions_tests.cpp
Comment thread src/app/extensions_tests.cpp
Comment thread src/app/extensions_tests.cpp

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/app/extensions.cpp
Comment thread src/app/extensions_tests.cpp
Comment thread src/app/file_system.cpp
@ckaiser ckaiser changed the title [WIP] Implement custom format support for extensions, saving and loading (#4950) Extension tests, add custom formats with saving and loading (#4950) Feb 27, 2026
@ckaiser ckaiser marked this pull request as ready for review February 27, 2026 19:56
@dacap

dacap commented Mar 2, 2026

Copy link
Copy Markdown
Member

There is an assert failing when running ctest in debug mode and gui available:

    Start 61: keyboard_shortcuts_tests
1/2 Test #61: keyboard_shortcuts_tests .........Subprocess aborted***Exception:   0.17 sec
FT: Loading font ... FAIL
FT: Loading font ... FAIL
src/app/extensions.cpp:995: Assertion failed: !item.widget->parent()

    Start 62: app_tests
2/2 Test #62: app_tests ........................Subprocess aborted***Exception:   0.15 sec
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from App
[ RUN      ] App.JustClose
FT: Loading font ... FAIL
FT: Loading font ... FAIL
src/app/extensions.cpp:995: Assertion failed: !item.widget->parent()

@dacap

dacap commented Mar 2, 2026

Copy link
Copy Markdown
Member

About the API, I think we should be able to specify an extension argument to newFileFormat(), and avoid name and extensions if the user wish (probably extension or extensions could be used indifferently as a string or as a table, accepting both types and both argument names). The name by default could be the first extension specified.

Comment thread src/app/extensions.cpp Outdated
@ZeWaka

ZeWaka commented Mar 2, 2026

Copy link
Copy Markdown

About the API, I think we should be able to specify an extension argument to newFileFormat(), and avoid name and extensions if the user wish (probably extension or extensions could be used indifferently as a string or as a table, accepting both types and both argument names). The name by default could be the first extension specified.

So there'd be both an extension and an extensions argument? Seems a bit confusing.

I do agree that you could allow extensions to accept a string for the simple case of one extension, though. Making name optional also makes sense.

@ckaiser ckaiser force-pushed the omelette-du-format branch 5 times, most recently from 38b7708 to 5727b66 Compare March 3, 2026 09:24
@ckaiser

ckaiser commented Mar 3, 2026

Copy link
Copy Markdown
Member Author

Fixed the test issues, although I couldn't reproduce the assert issue you were having @dacap, does this happen in a fresh profile? There could be an installed extension causing, maybe this check causes problems?

I've made name optional, and I changed away from unoredered_set for the extension list. Originally my idea was to use it just to get duplicate removal for free, but to make name optional I wanted to pick a stable name, so I might as well just use a standard vector and do a one-time check when registering the extension.

I also added the possibility to use a string or table in the extensions parameter and an extension parameter that takes a string (you can mix and match if... you wanna be weird about it)

Lastly, I removed the starting "." in some of the error messages regarding formats, so that they can be descriptive instead of just extension based, since this could potentially cause confusion (even with the aseprite format, if you were saving as .aseprite, the error message could mention .ase, or with .jpg/.jpeg. This could also help us eventually to have more descriptive names for formats which is helpful when you start introducing some extension-based ones.
Example:
image

@ckaiser

ckaiser commented Mar 5, 2026

Copy link
Copy Markdown
Member Author

Moved the arguments for onload and onsave to a single table and added clarification for formats in the error windows. (updated the extension for it)

As for id vs name, I think the latter is better since it's not really an identifier and it's not used elsewhere, it's just to show the user and it's optional.

@dacap

dacap commented Mar 10, 2026

Copy link
Copy Markdown
Member

I'm testing this right now, saw that the list of file formats is not sorted 👀

image

@ckaiser ckaiser force-pushed the omelette-du-format branch from 1cb2e6c to eedc484 Compare March 11, 2026 23:03
@ckaiser

ckaiser commented Mar 11, 2026

Copy link
Copy Markdown
Member Author

@dacap Fixed the sorting issue (did not realize we were using the FileFormatsManager order), and fixed the assert, the signals were not getting connected because AppMenus is initialized after extensions so we didn't have the instance available, changed that to a lambda instead of direct connection.

@dacap dacap left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry that I'm reviewing this in chunks, but I wasn't able to review all tests yet, it will take some time.

Comment thread src/dio/file_format.h Outdated
Comment thread src/dio/file_format.h Outdated
@dacap dacap assigned ckaiser and unassigned dacap Mar 17, 2026
@ckaiser ckaiser force-pushed the omelette-du-format branch from eedc484 to 274635e Compare March 19, 2026 09:41
@ckaiser ckaiser assigned dacap and unassigned ckaiser Mar 19, 2026

@dacap dacap left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't able to check all tests yet, but I've found several fixes that are required in onLoad/onSave mainly (and probably more tests are needed for coverage).

The main thing is that the Lua stack is not kept intact after a onLoad/onSave() on every path (happy path and exception paths). You can check with a helper like this:

  struct CheckLStack {
    lua_State* L;
    int top;
    CheckLStack(lua_State* L) : L(L), top(lua_gettop(L)) {}
    ~CheckLStack() {
      PRINTARGS("top", top, "lua_gettop", lua_gettop(L));
      ASSERT(top == lua_gettop(L));
    }
  };

  bool onLoad(FileOp* fop) override
  {
    script::Engine* engine = App::instance()->scriptEngine();
    lua_State* L = engine->luaState();
    CheckLStack check(L);
...

Comment thread src/app/script/plugin_class.cpp Outdated
Comment thread src/app/script/plugin_class.cpp Outdated
Comment thread src/dio/detect_format.cpp
Comment thread src/app/extensions.cpp Outdated
Comment thread src/app/extensions.cpp Outdated
Comment thread src/app/extensions.cpp
Comment thread src/app/extensions.cpp
Comment thread src/app/extensions.cpp Outdated
Comment thread src/app/extensions.cpp
Comment thread src/app/extensions_tests.cpp Outdated
@dacap dacap assigned ckaiser and unassigned dacap Mar 19, 2026
@ckaiser ckaiser force-pushed the omelette-du-format branch from 274635e to d91c65b Compare March 20, 2026 08:12

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/app/extensions_tests.cpp
Comment thread src/app/extensions_tests.cpp
@ckaiser

ckaiser commented Mar 20, 2026

Copy link
Copy Markdown
Member Author

Fixed all the the stack issues (I just kinda forgot about that huh, maybe we could add a debug-only macro with this stack-checking helper and sprinkle it all over the place so that I don't make that mistake again 🙈).

I've added some more tests for the format registration function and for general completeness and especially failure paths.

@ckaiser ckaiser assigned dacap and unassigned ckaiser Mar 20, 2026
@dacap

dacap commented Mar 20, 2026

Copy link
Copy Markdown
Member

maybe we could add a debug-only macro with this stack-checking helper and sprinkle it all over the place so that I don't make that mistake again 🙈).

Yeah, I was thinking the same (in the past I tried but avoid it because it's too much work for functions that return values). I'll add something after merging this PR.

@dacap dacap linked an issue Mar 20, 2026 that may be closed by this pull request
@dacap dacap added this to the v1.3.18-beta2 milestone Mar 20, 2026
@dacap

dacap commented Mar 20, 2026

Copy link
Copy Markdown
Member

Looks great! 🥂

@dacap dacap merged commit d91c65b into aseprite:beta Mar 20, 2026
12 checks passed
@ckaiser ckaiser deleted the omelette-du-format branch May 7, 2026 04:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expand plugin API to support loading/saving custom formats

4 participants