Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

62 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sai.swi

Swayimg API Improved

  • The sai (Japanese: 釵, lit. 'hairpin'; Chinese: 鐵尺, lit. 'iron ruler'…) [Wikipedia]
    • Provides the quality of life improvements and api aesthetics like a hairping
    • Is much more powerful and completely replaces the original api like a sword overrules a stick

Quick Overview

  • All basic features that swayimg should have by default.
    • shorter and easier to type when accessing the api
    • .swi simple way to say a lua package is made for swayimg - like .nvim for neovim
    • allows vim-style mappings - <C-S-Del>, <C-.>
    • eventloop system based on neovim lua autocommands - almost everything is listenable
    • all variables can now be set and read - no more caching of the last set value
    • simpler and efficient, yet offers more features and practicality than the original
  • Focus on extensibility and ease of use.
  • Custom modes! - exemplary usage of filtering mode:
recording.mp4

✨ Complete list of Features (click to expand)

  • options now accessible as variables: sai.text.size = sai.text.size*1.1
  • forward compatible: original api is still directly forwarded through sai so all additions are available and any setter/enabler and getter methods will automatically be accessible as variables, even if not documented yet.
  • common actions as directly mappable functions:
    v.map('Right', v.go.next) -- image
    v.map('k', v.pan.up)
    v.map('Alt+k', function() v.pan.by(70,70) end)
  • eventloop: subscribe to any change in the api and trigger your own events for messaging
    • inspired by vim event structure and neovim for registering the hooks in lua
  • exifdata loader:
    • gallery image lazy-loads metadata -> just like viewer mode
    • to load all, run local list=l.get(); require'exiv2'.load_all(list)
  • text layer templates:
    • track any api variable: g.text.topright={'Marked: {sai.imagelist.marked.size}'}
    • pretty-print exif data: v.text.topleft={'Exposure: {ExposureTime}'}
    • dynamic event updates - use eventloop hooks to update the text dynamically:
      v.text.topright={
        {event='User', pattern='help', function(ev)
          if not ev or not ev.data then return 'Ready to receive messages' end
          if type(ev.data) == 'string' then
            return 'Accepts multiline string:\t' .. ev.data
          elseif type(ev.data) == 'table' then
            table.insert(data, 1, 'Accepts lines as a table (keybind list):')
            return ev.data
          end
        end}
        [100] = 'Surely the message is shorter than 100 lines and won\'t override this'
      }
      e.trigger{event='User', match='help', data=U.str_bindlist(sai.mode.input)}
  • style-agnostic keybinds: use gui-, imv- or vim-style keybinds or any style that's right for you
    --        gui,      vim,    imv-gui, tripple-ctrl-click
    g.map({ 'Shift+m', '<S- >', 'Alt-h', 'C-3-LMB' }, function()
    	l.marked.set_current 'toggle'
    	g.go.left()
    end)
  • map shell commands directly with ranger-style file placeholders:
    • %f: '-quoted current file: v.map('Ctrl-e', 'xdg-open %f')
    • %s/%m: '-quoted marked/selected files: v.map('A-s', 'dragon-drop -x -A %s')
      • %s: fallbacks to current file
      • %m: doesn't execute the command if no files were marked
    • %: unquoted current (like in 4.x): v.map('', [[bash -c '$(which trash || echo rm) "%"']])
  • IPC: expose a Unix socket for external programs to evaluate Lua code in swayimg.
    local ipc = require 'sai.lib.ipc'
    local server = ipc.server('/tmp/swi.sock') -- auto-enabled
    local client = ipc.client('/tmp/swi.sock') -- auto-enabled
    print(client:send("return sai.text.size")) --> current font size
    client.enabled = false
    server.enabled = false

Custom modes

  • easily make temporary changes to anything in the api
  • all changes are active only while the custom mode is enabled - see snippets.two_pane_mode
  • make variable changes and optinally allow the user to adjust the user to adjust them
  • automatic event subscriptions and deletions
  • define custom keybinds with automatic help page displaying the keybinds

Sealed modes

These are modes that aren't meant to be extended or reused, they are just one singleton instance you can configure.

  • custom help mode that lets you see all available keybindings and live-updated settings Image of help mode in the settings section
  • command mode for live-evaluating lua code (example of extending input mode)
  • two-pane mode for comparing images (limited by the gallery scaling implementation)

Input mode

  • allows you to input arbitrary text and do whatever you want with it
  • multiline text
  • text selection
  • support for all common gui keyboard shortcuts
    • deletion (del prev word Ctrl+BS…)
    • jumping around (prev word Ctrl+Left, EOF Ctrl+End)
    • selection with Shift of everything for jumping (Shift+Left, Shift+Ctrl+Home)
    • clipboard support (select all Ctrl+a, Ctrl+c/v/x)

Filter mode

  • live filtering by exif data or any other image info
  • tab completion for image properties to filter by
  • configurable display options - what to live-update (completion, images, filter list…)
  • filtering by multiple metrics and operators
  • default config and basic usage (see <./mode/filter.lua> for more details):
    local fm = require('sai.mode.filter').new {
    	_location = 'topleft',
    	auto_help = true,
    	-- Public, changeable at any time
    	update_imagelist_on_confirm = true, ---Should imagelist be set to filtered images
    	live_imagelist = true, ---Should imagelist be updated with filtering
    	live_pager = true, ---Should a pager with the filtered files be displayed
    	---Should a pager with completion for the current tag be visible
    	---`'i'` for matching with ignored casing
    	tag_completion = true, ---@type false|'i'|true
    }
    g.map('/', function() fm.enabled = true end)

Custom default scaling modes

  • keep_xxx:

    • keeps image view size constant (depending on chosen metric) regardless of image resolution
    • useful for comparing identical images of different sizes
    • you will stay zoomed into the same spot of the image even if the other image is half the resolution
    • xxx can be replaced with any of the default scaling names or keep_size
  • add your own:

    table.insert(
      require('sai.api.viewer').custom_scale_handlers,
      ---@param self sai.api.viewer
      function(self, x)
        if type(x) ~= 'table' or not x.width or not x.height then return end
    
        e.subscribe {
          event = 'ImgChanged',
          match = 'viewer',
          callback = function(ev)
            if self._default_scale ~= x then return true end -- unsubscribe
    
            local img = ev.data or error()
            if x.width >= img.width and x.height >= img.height then return end
    
            local xscale = x.width / img.width
            local yscale = x.height / img.height
            self.super.set_abs_scale(math.min(xscale, yscale))
          end,
        }
    
        return 'real'
      end
    )
    v.default_scale = { width = 2560, height = 1440 }

TODOs

  • create PR to synchronize order of declaring variables/api to align with source code
  • create custom mode to dynamically pick which elements should be in each text corner

A collection of small code snippets that might be often wanted. Or can just serve as an inspiration for your own scripts.

Snippets include:

  • loading the current directory when swayimg opened with just 1 image
  • printing a status message on every variable change (like it used to be)
  • resizing the image with the window if the image is in not zoomed in
  • cycling fixed scaling and position modes
  • notifying on shell command output
  • pretty print tables - replace default tostring() method for better table conversion
  • command mode for live-executing lua with command history support
  • two-pane mode for viewing images side-by-side

⚠️ Limitations

True eventloop used by swayimg internally is still inaccessible. That means we cannot listen for file updates and save image state (like scale, position, etc.) before the image gets changed.

🚀 Geting Started

Clone the repo into your swayimg config to sai (not sai.swi!).

git clone https://github.com/litoj/sai.swi ~/.config/swayimg/sai

Don't forget to add it to .gitignore, if you version your dotfiles

You can add a keybind to update swayimg:

v.map('Alt+F5', require('sai.snippets').update) -- for just viewer mode

local map = require 'sai.binds' -- for any mode combo
map('a', 'Alt+F5', require('sai.snippets').update)

Use the API

To start using the api you only need to load the main module. However, if you also want to use all the main APIs as globals, you can also load sai.globals to have easier access to them. The structure is declared in types.lua

-- ~/.config/swayimg/init.lua
-- makes the api accessible through the `sai` global variable
-- you can also just save it to whatever you want
require 'sai.api.init'
-- or through first-letter globals (except: sai.imagelist -> `l` - not `i`)
require 'sai.api.globals'

-- now you can use all options as variables and make intricate behaviour using eventloop hooks

Better dev experience in NeoVim

If you're already using lua_ls you only need to include the original swayimg api definitions from which sai reuses the types:

settings.Lua.workspace.library = {'/usr/share/swayimg/swayimg.lua', '/usr/local/share/swayimg/swayimg.lua'}

License

Do whatever you please.

About

Swayimg api plugin focused on improving the base experience and extensibility

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages