Interacting with Marta APIs

Now you know how to create simple Marta plugins. This time let’s touch Marta APIs a bit more. In this tutorial we will create an action that shows a popup with some information about the selected files.

🚩 Create a new single-file plugin, declare a plugin and an action.

Contexts

Marta holds the application state in several kinds of contexts:

As we want to get the selected files, we surely need the instance of a PaneContext. How we can access it from our action?

In fact, there is one more context kind - an ActionContext, passed to Action.apply().

marta.action {
    ...
    apply = function(context)
        ...
    end
}

ActionContext holds references to PaneContext instances for both active and inactive panes. Let’s use it:

apply = function(context)
    -- PaneContext for the active pane
    local activePane = context.activePane

    -- Active pane list model
    local model = activePane.model

    -- Iterate through all active files
    -- Active files are selected files, if at least one file is selected,
    -- or a file under cursor. For an empty directory, `#activeFiles == 0`.
    for _, file in ipairs(model.activeFiles) do
        martax.alert(file.name)
    end
end

🚩 Put the code above to your action and check if everything works as expected. A popup should be shown for each selected file.

Collecting info

Let’s join the output data so only a single popup alert will be displayed. We will also add some more information about the items: for a directory, we will enclose the name into square brackets, for ordinary files, we will print the file size. So the final message will look like this:

[Documents]
[Library]
my-document.txt (4 KB)
script.sh (720 bytes)

martax module already provides a helper function for rendering size so we don’t need to write it by ourselves:

-- Prints "1 KB"
martax.formatSize(1024)

Instead of showing popups for each file, we collect results to the text variable:

apply = function(context)
    local files = context.activePane.model.activeFileInfos
    local text = ""

    for _, file in ipairs(files) do
        local name = file.name
        local entity

        if file.isFolder then
            entity = "[" .. name .. "]"
        else
            entity = name .. " (" .. martax.formatSize(file.size) .. ")"
        end

        text = text .. entity .. "\n"
    end

    martax.alert("Selected files: \n\n" .. text)
end

Note that we used activeFileInfos instead of activeFiles in the code fragment above. activeFiles returns an array of File objects which are bare pointers to files. They don’t store any file attributes. On the contrary, activeFileInfos returns an array of FileInfo with all the attributes we need. FileInfo attributes are gathered when the folder is loaded, so they’re cached.

Note that some attributes may be not available on particular file systems. For instance, ZIP file system doesn’t store macOS extended attributes, so isApplication will always return false.

Final version

Now everything looks good – except that we do not check if the selection is empty. Let’s add the check:

local files = context.activePane.model.activeFileInfos
if #files == 0 then
    martax.alert("No files selected.")
    return
end

We can also ask Marta not to run action at all by passing the isApplicable() function. It accepts an ActionContext, and returns a boolean. false means the action should not be executed.

isApplicable = function(context)
    return context.activePane.model.hasActiveFiles
end

Now, if you try to run the action inside an empty folder, no popup will appear. Instead, the "Show file information" can't be run in this context. message will be shown in a status bar.

isApplicable() is always called right before running an action, so you can be considerably safe avoiding some checks in apply(). However, we live in a multi-threaded world, and some other application (or even Marta, as it has an operation queue) might change the reality. So, safer is better.

Here’s the final version of our plugin:

plugin { 
    id = "marta.example.fileinfo",
    name = "File information",
    apiVersion = "2.0"
}

action {
    id = "show",
    name = "Show file information",

    isApplicable = function(context)
        return context.activePane.model.hasActiveFiles
    end,

    apply = function(context)
        local files = context.activePane.model.activeFileInfos
        if #files == 0 then
            martax.alert("No files selected.")
            return
        end

        local text = ""
        for _, file in ipairs(files) do
            local name = file.name
            local entity

            if file.isFolder then
                entity = "[" .. name .. "]"
            else
                entity = name .. " (" .. martax.formatSize(file.size) .. ")"
            end

            text = text .. entity .. "\n"
        end

        martax.alert("Files: \n\n" .. text)
    end
}