marta-file-manager / marta-issues

An issue tracker for Marta File Manager.
https://marta.yanex.org/
359 stars 0 forks source link

Close duplicate tabs #957

Closed eugenesvk closed 1 year ago

eugenesvk commented 1 year ago

upd here is a tab-deduplication plugin that fixes this issue as well as another one https://github.com/marta-file-manager/marta-issues/issues/967

Suppose I have 20 tabs opened and have two pairs of duplicate tabs, so they just waste tab space Is there an action/script I could write to enumerate the tabs and close the duplicates? Ideally, I'd like to run this action whenever I close the current tab (so override "Cmd+W" "core.tab.close" to perform two actions instead of just one) as I've found this to be a rather efficient way to get rid of the duplicates

page-down commented 1 year ago

Is there an action/script I could write ...

See PaneManager and TabManager https://marta.sh/api/marta/panemanager.type/ https://marta.sh/api/marta/tabmanager.type/

It should be a very simple Lua plugin implementation.

eugenesvk commented 1 year ago

Thanks for the suggestions re. tab enumeration, but how would I hook it into a "close tab" command? I couldn't find anything hook via google on the marta website. And a more general question: is there a list of Marta plugins somewhere I could check to discover what is possible and how it's done? FMan had this great discoverability feature via builtin plugin manager and also via simple github tabs (which this manager used))

page-down commented 1 year ago

... how would I hook it into a "close tab" command?

https://marta.sh/api/marta/tabmanager.type/close/

... is there a list of Marta plugins somewhere ...

I believe there is no such list. I remember in which thread I suggested building a user community, however there was no follow up, and there wasn't even a GitHub Discussion before.

Here is my simple plugin implementation to close duplicate tabs (only the currently active pane).

close-duplicated-tabs.lua

marta.expose()

marta.plugin { 
  id = "org.example.marta.close-duplicated-tabs",
  name = "Close Duplicated Tabs",
  apiVersion = "2.0"
}

marta.action {
  id = "close-duplicated-tabs",
  name = "Close Duplicated Tabs",
  apply = function(context)
    local tabManager = context.window.tabs
    local position = tabManager:getPosition(context.activePane)
    local count = tabManager:getCount(position)
    local t = {}
    local i = 0
    while (i < count) do
      local tab = tabManager:getTab(position, i)
      local path = tab.model.folder.path
      if (t[path] ~= nil) then
        tabManager:close(tab)
        count = count - 1
      else
        t[path] = true
        i = i + 1
      end
    end
  end
}

Simple and commonly used features like these should be built in, rather than leaving users searching around for a working plugin. (I believe that after some version update, the above code will no longer work.)

eugenesvk commented 1 year ago

Thanks a bunch for this example, though I'm still fuzzy on the hooking part:

... how would I hook it into a "close tab" command?

https://marta.sh/api/marta/tabmanager.type/close/

This says Close the tab., so as far as I understand it it's a method I'd use inside some action to close a tab. But what I need is whenever any close tab action (core.tab.close) is invoked to be notified via some callback. Or maybe not, I'll just replace core.tab.close with the plugin's command in the menus and keybinds and also add an additional action there to close the current tab, and then search for duplicates and close them. (as far as I understood, you can't invoke several actions in a menu/keybind, so you can't have a ["core.tab.close" "org.example.marta.close-duplicated-tabs. close-duplicated-tabs"] combination, correct?)

So my immediate issue can be resolved, but it you have an idea on how to register a callback, that'd still be valuable for some other plugin idea

Simple and commonly used features like these should be built in, rather than leaving users searching around for a working plugin. (I believe that after some version update, the above code will no longer work.)

Oh, I'd be happy for it to be the case, but the beauty of plugins is that they allow to achieve that much earlier :)

page-down commented 1 year ago

... I'm still fuzzy on the hooking part ... ... how to register a callback ...

As far as I know there is currently no mechanism like event listeners (e.g. events for closing tabs) to register and perform other actions.

I'd like to run this action whenever I close ... so override ... to perform two actions ...

The above code only completes the action of removing the duplicate tabs, so you also need to add tabManager:close(PUT_CURRENT_ACTIVE_PANE_HERE) before starting the count and removing the duplicate tabs.

Or write another action to call runAction to run multiple actions in sequence. https://marta.sh/api/marta/windowcontext.type/runaction/

... you can't invoke several actions ...

For executing multiple actions in a row (not hook as you mentioned), see my Action Chains feature suggestion below.

https://github.com/marta-file-manager/marta-issues/issues/785

eugenesvk commented 1 year ago

Or write another action to call runAction to run multiple actions in sequence.

what do you pass to this function to run an action that you've already defined?

--???: how to use window:runAction context to pass an action there?
marta.action { id="✗tab_cur←", name="Close Current Tab and switch to the Left Tab",
  apply = function(ctxA)
    local ctxPaneA  = ctxA.activePane
    local ctxW      = ctxA.window
    -- action: Action, context:[PaneContext|ActionContext]?=nil
    -- ctxW:runAction("es¦tab.✗tab_dupe") -- ERR: 'apply()' failed: Unexpected type: userData expected, got string
    -- ctxW:runAction(tabLeft(ctxA))      -- ERR: Unexpected argument count: [1, 2] expected, got 1, 2
    -- ERR↓  'apply()' failed: Unexpected argument count: [1, 2] expected, got 1, 2
    -- ctxW:runAction(action {id="action", name="Hello world", apply    = function() martax.alert("Hello, world!") end })
    ctxW:runAction(action {id="tab←"})      -- ERR: 'apply()' failed: Unexpected type: string expected, got null
  end
}

This is my full plugin

--???: how to use window:runAction context to pass an action there?

--2do: make tabCloseDupe close the other tab instead of the current one:
  -- saveCur fails due to a bug:
  -- a@0 a@1 b@2 a@3  when a@3 is active: closing a@1 by the function selects b@2 instead of keeping the selection at a@3
  -- https://github.com/marta-file-manager/marta-issues/issues/969
-- "Cmd+W"  "es¦tab.✗tab_n_dupe"
  -- to ✗close the current tab, select 1 tab to the ←, ✗close all duplicate tabs in the active pane
marta.expose()
marta.plugin { id="es¦tab", name="Close Duplicated Tabs", apiVersion="2.1"}

marta.action {id="✗tab_n_dupe", name="Close Current Tab and Duplicated Tabs",
  apply = function(ctxA) tabClose(ctxA); tabLeft(ctxA); tabCloseDupe(ctxA); end }
marta.action {id="✗tab_dupe" , name="Close Duplicated Tabs" , apply = function(ctxA) tabCloseDupe({ctxA=ctxA, saveCur=true}); end }
marta.action {id="✗tab_cur"  , name="Close Current Tab"     , apply = function(ctxA) tabClose    (ctxA); end }
marta.action {id="tab←"      , name="Switch to the Left Tab", apply = function(ctxA) tabLeft     (ctxA); end }

--???: how to use window:runAction context to pass an action there?
marta.action { id="✗tab_cur←", name="Close Current Tab and switch to the Left Tab",
  apply = function(ctxA)
    local ctxPaneA  = ctxA.activePane
    local ctxW      = ctxA.window
    -- action: Action, context:[PaneContext|ActionContext]?=nil
    -- ctxW:runAction("es¦tab.✗tab_dupe") -- ERR: 'apply()' failed: Unexpected type: userData expected, got string
    -- ctxW:runAction(tabLeft(ctxA))      -- ERR: Unexpected argument count: [1, 2] expected, got 1, 2
    -- ERR↓  'apply()' failed: Unexpected argument count: [1, 2] expected, got 1, 2
    -- ctxW:runAction(action {id="action", name="Hello world", apply    = function() martax.alert("Hello, world!") end })
    ctxW:runAction(action {id="tab←"})      -- ERR: 'apply()' failed: Unexpected type: string expected, got null
  end
}

function tabLeft(ctxA)
  local ctxPaneA    = ctxA.activePane
  local tabMan      = ctxA.window.tabs
  local tabPos      = tabMan:getPosition(ctxPaneA)  --(tab: PaneContext): Option<TabPosition> -- Get the tab position
  local tabActI     = tabMan:getActiveTabIndex(tabPos) -- 0-based active tab index
  local tabLeft     = tabMan:getTab           (tabPos, tabActI-1)
  if (tabActI > 0) then -- this might not be needed since left-most tab receiving -1 command doesn't error
    tabMan:activate(tabLeft)
  end
end

function tabClose(ctxA)
  local ctxPaneA    = ctxA.activePane
  local tabMan      = ctxA.window.tabs
  tabMan:close(ctxPaneA)
end

function tabCloseDupe(arg)
  local ctxA        = arg.ctxA
  local saveCur     = arg.saveCur or false -- if current tab is a dupe, close the other dupe
  local ctxPaneA    = ctxA.activePane
  local tabMan      = ctxA.window.tabs
  local tabPos      = tabMan:getPosition      (ctxPaneA) --(tab: PaneContext): Option<TabPosition> -- Get the tab position
  local tabCount    = tabMan:getCount         (tabPos  ) --(position: Option<TabPosition>): Int tab count for a given position
  local t           = {}
  local i           = 0
  while (i < tabCount) do
    local tab    = tabMan:getTab(tabPos, i)
    local path   = tab.model.folder.path
    if (t[path] ~= nil) then
      -- if saveCur then -- fails due to a bug, implement later when it's fixed
      --   local tabActI    = tabMan:getActiveTabIndex(tabPos  ) -- 0-based active tab index
      --   martax.alert("i=" .. tostring(i) .. " tabActI=" .. tostring(tabActI))
      --   if i==tabActI then
      --     print('aaa')
      --   end
      -- end
      tabMan:close(tab)
      tabCount   = tabCount - 1
    else
      t[path]   = {true,i} -- path has been seen @ i (use it later to close i instead of the current tab)
      i         = i + 1
    end
  end
end
eugenesvk commented 1 year ago

For executing multiple actions in a row (not hook as you mentioned), see my Action Chains feature suggestion below.

By the way, isn't this

Run Actions actions – actions to run (array)

supposed to allow chains of actions? Though "q" {id "core.run.actions" actions ["core.tab.new" "core.tab.new"]} didn't work and I couldn't find any Run Actions in the Action Panel (so maybe my ID here is wrong and it's core.actions.run or something)

page-down commented 1 year ago

what do you pass to this function to ...

According to the documentation you need to provide an Action instance.

For example:

windowContext:runAction(marta.globalContext.actions:getById("core.back"), activePane)

By the way, isn't this ... actions – actions to run (array)

I have good reason to believe that this is an outdated document.

Need to wait for yanex to clean up.

eugenesvk commented 1 year ago

According to the documentation you need to provide an Action instance.

That part I figured out myself :)

windowContext:runAction(marta.globalContext.actions:getById("core.back"), activePane)

That's what I was missing, thanks for your help!

eugenesvk commented 1 year ago

By the way, is it possible to pass any parameters for these builtin actions? windowContext:runAction doesn't seem to have a field for that, but maybe there is a trick