Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type annotations #29

Merged
merged 1 commit into from
May 1, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 99 additions & 2 deletions init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ PaperWM.author = "Michael Mogenson"
PaperWM.homepage = "https://github.com/mogenson/PaperWM.spoon"
PaperWM.license = "MIT - https://opensource.org/licenses/MIT"

-- Types

---@alias PaperWM table PaperWM module object
---@alias Window userdata a ui.window
---@alias Frame table hs.geometry rect
---@alias Index { row: number, col: number, space: number }
---@alias Space number a Mission Control space ID
---@alias Screen userdata hs.screen

---@alias Mapping { [string]: (table | string)[]}
PaperWM.default_hotkeys = {
stop_events = { { "alt", "cmd", "shift" }, "q" },
refresh_windows = { { "alt", "cmd", "shift" }, "r" },
Expand Down Expand Up @@ -115,6 +125,7 @@ PaperWM.screen_margin = 1
PaperWM.logger = hs.logger.new(PaperWM.name)

-- constants
---@enum Direction
local Direction <const> = {
LEFT = -1,
RIGHT = 1,
Expand All @@ -134,6 +145,9 @@ local ui_watchers = {} -- dictionary of uielement watchers with window id for ke
-- refresh window layout on screen change
local screen_watcher = Screen.watcher.new(function() PaperWM:refreshWindows() end)

---get the Mission Control space for the provided index
---@param index number index for Mission Control space
---@return Space|nil
local function getSpace(index)
local layout = Spaces.allSpaces()
for _, screen in ipairs(Screen.allScreens()) do
Expand All @@ -144,6 +158,10 @@ local function getSpace(index)
end
end

---return the leftmost window that's completely on the screen
---@param columns Window[] a column of windows
---@param screen Frame the coordinates of the screen
---@return Window|nil
local function getFirstVisibleWindow(columns, screen)
local x = screen:frame().x
for _, windows in ipairs(columns or {}) do
Expand All @@ -152,12 +170,24 @@ local function getFirstVisibleWindow(columns, screen)
end
end

---get a column of windows for a space from the window_list
---@param space Space
---@param col number
---@return Window[]
local function getColumn(space, col) return (window_list[space] or {})[col] end

---get a window in a row, in a column, in a space from the window_list
---@param space Space
---@param col number
---@param row number
---@return Window
local function getWindow(space, col, row)
return (getColumn(space, col) or {})[row]
end

---get the tileable bounds for a screen
---@param screen Screen
---@return Frame
local function getCanvas(screen)
local screen_frame = screen:frame()
return Rect(screen_frame.x + PaperWM.window_gap,
Expand All @@ -166,6 +196,9 @@ local function getCanvas(screen)
screen_frame.h - (2 * PaperWM.window_gap))
end

---update the column number in window_list to be ascending from provided column up
---@param space Space
---@param column number
local function updateIndexTable(space, column)
local columns = window_list[space] or {}
for col = column, #columns do
Expand All @@ -175,8 +208,13 @@ local function updateIndexTable(space, column)
end
end

local focused_window = nil
local pending_window = nil
local focused_window = nil ---@type Window|nil
local pending_window = nil ---@type Window|nil

---callback for window events
---@param window Window
---@param event string name of the event
---@param self PaperWM
local function windowEventHandler(window, event, self)
self.logger.df("%s for [%s] id: %d", event, window, window and window:id() or -1)
local space = nil
Expand Down Expand Up @@ -223,6 +261,9 @@ local function windowEventHandler(window, event, self)
if space then self:tileSpace(space) end
end

---make the specified space the active space
---@param space Space
---@param window Window|nil a window in the space
local function focusSpace(space, window)
local screen = Screen(Spaces.spaceDisplay(space))
if not screen then
Expand Down Expand Up @@ -250,6 +291,8 @@ local function focusSpace(space, window)
end
end

---start automatic window tiling
---@return PaperWM
function PaperWM:start()
-- check for some settings
if not Spaces.screensHaveSeparateSpaces() then
Expand Down Expand Up @@ -278,6 +321,8 @@ function PaperWM:start()
return self
end

---stop automatic window tiling
---@return PaperWM
function PaperWM:stop()
-- stop events
self.window_filter:unsubscribeAll()
Expand All @@ -287,6 +332,14 @@ function PaperWM:stop()
return self
end

---tile a column of window by moving and resizing
---@param windows Window[] column of windows
---@param bounds Frame bounds to constrain column of tiled windows
---@param h number|nil set windows to specified height
---@param w number|nil set windows to specified width
---@param id number|nil id of window to set specific height
---@param h4id number|nil specific height for provided window id
---@return number width of tiled column
function PaperWM:tileColumn(windows, bounds, h, w, id, h4id)
local last_window, frame
for _, window in ipairs(windows) do
Expand Down Expand Up @@ -319,6 +372,8 @@ function PaperWM:tileColumn(windows, bounds, h, w, id, h4id)
return w -- return width of column
end

---tile all column in a space by moving and resizing windows
---@param space Space
function PaperWM:tileSpace(space)
if not space or Spaces.spaceType(space) ~= "user" then
self.logger.e("current space invalid")
Expand Down Expand Up @@ -407,6 +462,7 @@ function PaperWM:tileSpace(space)
end
end

---get all windows across all spaces and retile them
function PaperWM:refreshWindows()
-- get all windows across spaces
local all_windows = self.window_filter:getWindows()
Expand All @@ -430,6 +486,9 @@ function PaperWM:refreshWindows()
for space, _ in pairs(retile_spaces) do self:tileSpace(space) end
end

---add a new window to be tracked and automatically tiled
---@param add_window Window new window to be added
---@return Space|nil space that contains new window
function PaperWM:addWindow(add_window)
-- A window with no tabs will have a tabCount of 0
-- A new tab for a window will have tabCount equal to the total number of tabs
Expand Down Expand Up @@ -489,6 +548,10 @@ function PaperWM:addWindow(add_window)
return space
end

---remove a window from being tracked and automatically tiled
---@param remove_window Window window to be removed
---@param skip_new_window_focus boolean|nil don't focus a nearby window if true
---@return Space|nil space that contained removed window
function PaperWM:removeWindow(remove_window, skip_new_window_focus)
-- get index of window
local remove_index = index_table[remove_window:id()]
Expand Down Expand Up @@ -528,6 +591,9 @@ function PaperWM:removeWindow(remove_window, skip_new_window_focus)
return remove_index.space -- return space for removed window
end

---move focus to a new window next to the currently focused window
---@param direction Direction use either Direction UP, DOWN, LEFT, or RIGHT
---@param focused_index Index index of focused window within the window_list
function PaperWM:focusWindow(direction, focused_index)
if not focused_index then
-- get current focused window
Expand Down Expand Up @@ -570,6 +636,11 @@ function PaperWM:focusWindow(direction, focused_index)
return new_focused_window
end

---swap the focused window with a window next to it
---if swapping horizontally and the adjacent window is in a column, swap the
---entire column. if swapping vertically and the focused window is in a column,
---swap positions within the column
---@param direction Direction use Direction LEFT, RIGHT, UP, or DOWN
function PaperWM:swapWindows(direction)
-- use focused window as source window
local focused_window = Window.focusedWindow()
Expand Down Expand Up @@ -677,6 +748,8 @@ function PaperWM:swapWindows(direction)
self:tileSpace(focused_index.space)
end

---move the focused window to the center of the screen, horizontally
---don't resize the window or change it's vertical position
function PaperWM:centerWindow()
-- get current focused window
local focused_window = Window.focusedWindow()
Expand All @@ -699,6 +772,8 @@ function PaperWM:centerWindow()
self:tileSpace(space)
end

---set the focused window to the width of the screen
---don't change the height
function PaperWM:setWindowFullWidth()
-- get current focused window
local focused_window = Window.focusedWindow()
Expand All @@ -718,6 +793,10 @@ function PaperWM:setWindowFullWidth()
self:tileSpace(space)
end

---resize the width or height of the window, keeping the other dimension the
---same. cycles through the ratios specified in PaperWM.window_ratios
---@param direction Direction use Direction.WIDTH or Direction.HEIGHT
---@param cycle_direction Direction use Direction.ASCENDING or DESCENDING
function PaperWM:cycleWindowSize(direction, cycle_direction)
-- get current focused window
local focused_window = Window.focusedWindow()
Expand Down Expand Up @@ -788,6 +867,8 @@ function PaperWM:cycleWindowSize(direction, cycle_direction)
self:tileSpace(space)
end

---take the current focused window and move it into the bottom of
---the column to the left
function PaperWM:slurpWindow()
-- TODO paperwm behavior:
-- add top window from column to the right to bottom of current column
Expand Down Expand Up @@ -850,6 +931,8 @@ function PaperWM:slurpWindow()
self:tileSpace(focused_index.space)
end

---remove focused window from it's current column and place into
---a new column to the right
function PaperWM:barfWindow()
-- TODO paperwm behavior:
-- remove bottom window of current column
Expand Down Expand Up @@ -901,6 +984,8 @@ function PaperWM:barfWindow()
self:tileSpace(focused_index.space)
end

---switch to a Mission Control space
---@param index number incremental id for space
function PaperWM:switchToSpace(index)
local space = getSpace(index)
if not space then
Expand All @@ -912,6 +997,8 @@ function PaperWM:switchToSpace(index)
focusSpace(space)
end

---switch to a Mission Control space to the left or right of current space
---@param direction Direction use Direction.LEFT or Direction.RIGHT
function PaperWM:incrementSpace(direction)
if (direction ~= Direction.LEFT and direction ~= Direction.RIGHT) then
self.logger.d("move is invalid, left and right only")
Expand Down Expand Up @@ -940,6 +1027,8 @@ function PaperWM:incrementSpace(direction)
end
end

---move focused window to a Mission Control space
---@param index number ID for space
function PaperWM:moveWindowToSpace(index)
local focused_window = Window.focusedWindow()
if not focused_window then
Expand Down Expand Up @@ -985,6 +1074,10 @@ function PaperWM:moveWindowToSpace(index)
focusSpace(new_space, focused_window)
end

---move and resize a window to the coordinates specified by the frame
---disable watchers while window is moving and re-enable after
---@param window Window window to move
---@param frame Frame coordinates to set window size and location
function PaperWM:moveWindow(window, frame)
-- greater than 0.017 hs.window animation step time
local padding <const> = 0.02
Expand All @@ -1007,6 +1100,7 @@ function PaperWM:moveWindow(window, frame)
end)
end

---supported window movement actions
PaperWM.actions = {
stop_events = partial(PaperWM.stop, PaperWM),
refresh_windows = partial(PaperWM.refreshWindows, PaperWM),
Expand Down Expand Up @@ -1048,6 +1142,9 @@ PaperWM.actions = {
move_window_9 = partial(PaperWM.moveWindowToSpace, PaperWM, 9)
}

---bind userdefined hotkeys to PaperWM actions
---use PaperWM.default_hotkeys for suggested defaults
---@param mapping Mapping table of actions and hotkeys
function PaperWM:bindHotkeys(mapping)
local spec = self.actions
hs.spoons.bindHotkeysToSpec(spec, mapping)
Expand Down