diff --git a/users/wpcarro/.envrc b/users/wpcarro/.envrc new file mode 100644 index 000000000..23adf2d29 --- /dev/null +++ b/users/wpcarro/.envrc @@ -0,0 +1,8 @@ +export BRIEFCASE="$(realpath .)" +# I'm ensuring that $NIX_PATH is mostly empty, so that I only depend on +# for now. +# For more information on the NIX_PATH anti-pattern, see here: +# https://nix.dev/tutorials/towards-reproducibility-pinning-nixpkgs.html#pinning-nixpkgs +export NIX_PATH="briefcase=$BRIEFCASE"; +export DESKTOP="zeno.lon.corp.google.com"; +export LAPTOP="seneca"; diff --git a/users/wpcarro/.gitignore b/users/wpcarro/.gitignore new file mode 100644 index 000000000..0132f3895 --- /dev/null +++ b/users/wpcarro/.gitignore @@ -0,0 +1,31 @@ +.vim +./configs/secrets +**/*/.emacs.d/quelpa/**/* +**/*/.emacs.d/elpa/**/* +**/*/.emacs.d/emojis +**/*/.emacs.d/auto-save-list/**/* +**/*/.emacs.d/eshell/ +**/*/.emacs.d/var/**/* +**/*/.emacs.d/.cache/**/* +**/*/.emacs.d/request +**/*/.emacs.d/network-security.data +**/*/.emacs.d/smex-items +**/*/.gnupg/random_seed +.netrwhist +Vundle.vim +**/*/.emacs.d/custom.el +**/*/.emacs.d/projectile-bookmarks.eld +**/*/.emacs.d/bookmarks +**/*/transient/history.el +*.hi +*.o +__pycache__ +*.class +node_modules/ +/configs/.config/fish/config.fish +/configs/.config/fish/fish_variables +/website/blog/public/ +/emacs/.emacs.d/tramp +.gitsecret/keys/random_seed +!*.secret +secrets.json diff --git a/users/wpcarro/.gitsecret/keys/pubring.kbx b/users/wpcarro/.gitsecret/keys/pubring.kbx new file mode 100644 index 000000000..692d5c67b Binary files /dev/null and b/users/wpcarro/.gitsecret/keys/pubring.kbx differ diff --git a/users/wpcarro/.gitsecret/keys/pubring.kbx~ b/users/wpcarro/.gitsecret/keys/pubring.kbx~ new file mode 100644 index 000000000..c0a748ce2 Binary files /dev/null and b/users/wpcarro/.gitsecret/keys/pubring.kbx~ differ diff --git a/users/wpcarro/.gitsecret/keys/trustdb.gpg b/users/wpcarro/.gitsecret/keys/trustdb.gpg new file mode 100644 index 000000000..369485be0 Binary files /dev/null and b/users/wpcarro/.gitsecret/keys/trustdb.gpg differ diff --git a/users/wpcarro/.gitsecret/paths/mapping.cfg b/users/wpcarro/.gitsecret/paths/mapping.cfg new file mode 100644 index 000000000..fda2c84fb --- /dev/null +++ b/users/wpcarro/.gitsecret/paths/mapping.cfg @@ -0,0 +1 @@ +secrets.json:7d596a3ed16403040d89dd7e033a2af58e7aaabb6f246f44751b80a1863a2949 diff --git a/users/wpcarro/.skip-subtree b/users/wpcarro/.skip-subtree new file mode 100644 index 000000000..968b9b74d --- /dev/null +++ b/users/wpcarro/.skip-subtree @@ -0,0 +1,2 @@ +Do not recurse from top-level readTree, while this is being refactored +we have a nested tree. diff --git a/users/wpcarro/Makefile b/users/wpcarro/Makefile new file mode 100644 index 000000000..6ef5116e3 --- /dev/null +++ b/users/wpcarro/Makefile @@ -0,0 +1,9 @@ +install: + source "${BRIEFCASE}/configs/install" + +uninstall: + source "${BRIEFCASE}/configs/uninstall" + +list-broken-links: + find "${HOME}" -maxdepth 1 -xtype l && \ + find "${HOME}/.config" -maxdepth 1 -xtype l diff --git a/users/wpcarro/README.md b/users/wpcarro/README.md new file mode 100644 index 000000000..adfe30ff3 --- /dev/null +++ b/users/wpcarro/README.md @@ -0,0 +1,70 @@ +# briefcase + +[![Build status](https://badge.buildkite.com/aa0d413bfeedcafd8719f977eadd40e04d0b5334fc7f58e8ee.svg)](https://buildkite.com/wpcarros-infrastructure/post-receive) + +Welcome to my monorepo: briefcase. + +Herein you will find a variety of libraries, packages, and documents. Some of +this work in finished and other work is incomplete or just a sketch for a +future project. + +Where applicable, I try to include `README.md` files in some of the +subdirectories to help orient both myself and any onlookers. + +## Languages + +To give you a general idea of the source code inside of this monorepo, here is +the latest output from `tokei --hidden --sort code .`: + +```text +------------------------------------------------------------------------------- + Language Files Lines Code Comments Blanks +------------------------------------------------------------------------------- + Emacs Lisp 81 22267 13847 5661 2759 + Python 177 10575 7930 885 1760 + Elm 34 5345 4277 219 849 + Haskell 50 4263 3111 428 724 + Nix 66 1581 1379 66 136 + TypeScript 19 1345 1067 90 188 + Go 17 1256 926 173 157 + Vim Script 2 766 470 87 209 + Elixir 13 358 301 8 49 + JavaScript 9 77 73 0 4 + Lisp 3 83 43 23 17 + Shell 3 55 30 11 14 + Clojure 2 10 8 0 2 + C 1 6 5 0 1 + Rust 1 5 3 1 1 +------------------------------------------------------------------------------- + Total 478 47992 33470 7652 6870 +------------------------------------------------------------------------------- +``` + +## Sign posts + +Below I have outlined a few projects that you might find interesting. I am +using `//` to indicate the root of my monorepo, the directory in which this +`README.md` resides. + +- `//boilerplate`: scaffolding for projects. Boilerplate's goal is to + reduce the startup costs of a project. +- `//configs`: my dotfiles (e.g. `config.fish`, `init.vim`). +- `//emacs`: Emacs is both my preferred text editor and my window manager; with + tens of thousands of lines of Emacs Lisp, you can safely assume that this + directory hosts a lot of libraries and packages. +- `//monzo_ynab`: `systemd` timer unit that imports my Monzo (i.e. a U.K.-based + online bank) transactions into the personal finance tool YNAB (i.e. + youneedabudget.com). +- `//nixos`: my declarative configuration for my NixOS machines. If you are + unfamiliar with Nix, I recommend reading about the NixOS project. +- `//tools`: some scripts and projects that simplify my life. +- `//website`: everything required to build my website, wpcarro.dev. + +## Notes to self + +Here are a few reminders when setting up a new machine: + +- Ensure `~/.password-store` exists. +- Run `export_gpg` from a computer with my gpg credentials. Run `import_gpg` + from the new machine. +- Ensure the new machine can access my Github. diff --git a/users/wpcarro/assessments/brilliant/.ghci b/users/wpcarro/assessments/brilliant/.ghci new file mode 100644 index 000000000..efc88e630 --- /dev/null +++ b/users/wpcarro/assessments/brilliant/.ghci @@ -0,0 +1,2 @@ +:set prompt "> " +:set -Wall diff --git a/users/wpcarro/assessments/brilliant/App.hs b/users/wpcarro/assessments/brilliant/App.hs new file mode 100644 index 000000000..0272988f3 --- /dev/null +++ b/users/wpcarro/assessments/brilliant/App.hs @@ -0,0 +1,41 @@ +-------------------------------------------------------------------------------- +module App where +-------------------------------------------------------------------------------- +import Keyboard (Keyboard(..)) +import Transforms (Transform(..)) +import Utils ((|>)) + +import qualified Data.Char as Char +import qualified Utils +import qualified Data.List.Split as Split +import qualified Keyboard +import qualified Data.HashMap.Strict as HM +-------------------------------------------------------------------------------- + +transform :: Keyboard -> Transform -> Keyboard + +transform (Keyboard xs) xform = + case xform of + HorizontalFlip -> + xs + |> fmap reverse + |> Keyboard + + VerticalFlip -> + xs + |> reverse + |> Keyboard + + Shift n -> + xs + |> concat + |> Utils.rotate n + |> Split.chunksOf 10 + |> Keyboard + +retypePassage :: String -> Keyboard -> Maybe String +retypePassage passage newKeyboard = + passage + |> fmap Char.toUpper + |> traverse (\c -> HM.lookup c Keyboard.charToCoord) + >>= traverse (Keyboard.coordToChar newKeyboard) diff --git a/users/wpcarro/assessments/brilliant/Keyboard.hs b/users/wpcarro/assessments/brilliant/Keyboard.hs new file mode 100644 index 000000000..13b5de014 --- /dev/null +++ b/users/wpcarro/assessments/brilliant/Keyboard.hs @@ -0,0 +1,58 @@ +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +-------------------------------------------------------------------------------- +module Keyboard where +-------------------------------------------------------------------------------- +import Utils +import Data.Coerce +import Data.Hashable (Hashable) +import GHC.Generics (Generic) + +import qualified Data.List as List +import qualified Data.HashMap.Strict as HM +-------------------------------------------------------------------------------- + +newtype Keyboard = Keyboard [[Char]] + deriving (Eq) + +instance Show Keyboard where + show (Keyboard xxs) = + xxs |> fmap printRow |> List.intercalate "\n" + where + printRow :: [Char] -> String + printRow xs = + xs |> fmap (\x -> '[':x:']':"") |> List.intercalate "" + +data Coord = Coord + { row :: Int + , col :: Int + } deriving (Eq, Show, Generic) + +instance Hashable Coord + +-- | List of characters to their QWERTY coordinatees. +coords :: [(Char, Coord)] +coords = + qwerty + |> coerce + |> fmap (zip [0..]) + |> zip [0..] + |> fmap (\(row, xs) -> xs |> fmap (\(col, char) -> (char, Coord row col))) + |> mconcat + +-- | Mapping of characters to their coordinates on a QWERTY keyboard with the +-- top-left corner as 0,0. +charToCoord :: HM.HashMap Char Coord +charToCoord = HM.fromList coords + +coordToChar :: Keyboard -> Coord -> Maybe Char +coordToChar (Keyboard xxs) Coord{..} = + Just $ xxs !! row !! col + +qwerty :: Keyboard +qwerty = Keyboard [ ['1','2','3','4','5','6','7','8','9','0'] + , ['Q','W','E','R','T','Y','U','I','O','P'] + , ['A','S','D','F','G','H','J','K','L',';'] + , ['Z','X','C','V','B','N','M',',','.','/'] + ] diff --git a/users/wpcarro/assessments/brilliant/Main.hs b/users/wpcarro/assessments/brilliant/Main.hs new file mode 100644 index 000000000..e94c73bea --- /dev/null +++ b/users/wpcarro/assessments/brilliant/Main.hs @@ -0,0 +1,43 @@ +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Main where +-------------------------------------------------------------------------------- +import Options.Applicative +import Data.Semigroup ((<>)) + +import qualified Transforms +import qualified Keyboard +import qualified App +-------------------------------------------------------------------------------- + +data CommandArgs = CommandArgs + { transforms :: String + , passage :: String + } deriving (Eq, Show) + +parseArgs :: Parser CommandArgs +parseArgs = + CommandArgs <$> strOption + ( long "transforms" + <> short 't' + <> help "String of transforms where (e.g. \"HHVS12VHVHS3\")" ) + <*> strOption + ( long "passage" + <> short 'p' + <> help "Input text to re-type" ) + +main :: IO () +main = do + CommandArgs{..} <- execParser opts + case Transforms.fromString transforms of + Nothing -> putStrLn "You must provide valid input (e.g. \"HHVS12VHVHS3\")" + Just xs -> do + let keyboard = foldl App.transform Keyboard.qwerty (Transforms.optimize xs) + putStrLn $ "Typing: \"" ++ passage ++ "\"\nOn this keyboard:\n" ++ show keyboard + case App.retypePassage passage keyboard of + Nothing -> putStrLn $ "Looks like at least one of the characters in your input passage doesn't fit on our QWERTY keyboard: \n" ++ show Keyboard.qwerty + Just result -> putStrLn $ "Result: " ++ result + where + opts = info (parseArgs <**> helper) + ( fullDesc + <> progDesc "Transform a QWERTY keyboard using a string of commands") diff --git a/users/wpcarro/assessments/brilliant/README.md b/users/wpcarro/assessments/brilliant/README.md new file mode 100644 index 000000000..60d7de4e2 --- /dev/null +++ b/users/wpcarro/assessments/brilliant/README.md @@ -0,0 +1,82 @@ +# Transform QWERTY + +Apply a series of transforms to a QWERTY keyboard then use the new keyboard to +re-type a passage of text. + +## Environment + +You will need [Nix][nix] to build this program on your machine. The good news is +that you won't need any Haskell-specific dependencies like `ghc`, `cabal`, or +`stack`: just Nix. + +Once you have Nix installed, to build the program, run the following from this +project's top-level directory: + +```shell +$ nix-build +``` + +This should output an executable named `transform-keyboard` within a `result` +directory: + +```shell +$ tree result +result +└── transform-keyboard +``` + +### Testing + +To run the test suite, run the following from the project's top-level directory: + +```shell +$ nix-shell +$ runhaskell Spec.hs +``` + +[nix]: https://nixos.org/download.html + +## Usage + +Here are some `--help` and usage examples: + +```shell +$ ./result/transform-keyboard --help +Usage: transform-keyboard (-t|--transforms ARG) (-p|--passage ARG) + Transform a QWERTY keyboard using a string of commands + +Available options: + -t,--transforms ARG String of transforms where (e.g. "HHVS12VHVHS3") + -p,--passage ARG Input text to re-type + -h,--help Show this help text +``` + +Now a working example: + +```shell +$ ./result/transform-keyboard --transforms=HHVS12VHVHS3 --passage='Hello,Brilliant.' +Typing: "Hello,Brilliant." +On this keyboard: +[H][J][K][L][;][Q][W][E][R][T] +[Y][U][I][O][P][1][2][3][4][5] +[6][7][8][9][0][Z][X][C][V][B] +[N][M][,][.][/][A][S][D][F][G] +Result: ZIVV4D/O3VV36APF +``` + +...and an example with an erroneous input (i.e. `!`): + +```shell +$ ./result/transform-keyboard --transforms=HHVS12VHVHS3 --passage='Hello,Brilliant!' +Typing: "Hello,Brilliant!" +On this keyboard: +[H][J][K][L][;][Q][W][E][R][T] +[Y][U][I][O][P][1][2][3][4][5] +[6][7][8][9][0][Z][X][C][V][B] +[N][M][,][.][/][A][S][D][F][G] +Looks like at least one of the characters in your input passage doesn't fit on our QWERTY keyboard: +[1][2][3][4][5][6][7][8][9][0] +[Q][W][E][R][T][Y][U][I][O][P] +[A][S][D][F][G][H][J][K][L][;] +[Z][X][C][V][B][N][M][,][.][/] +``` diff --git a/users/wpcarro/assessments/brilliant/Spec.hs b/users/wpcarro/assessments/brilliant/Spec.hs new file mode 100644 index 000000000..e99e02564 --- /dev/null +++ b/users/wpcarro/assessments/brilliant/Spec.hs @@ -0,0 +1,103 @@ +-------------------------------------------------------------------------------- +module Spec where +-------------------------------------------------------------------------------- +import Test.Hspec +import Test.QuickCheck +import Keyboard (Keyboard(..)) +import Transforms (Transform(..)) +import Data.Coerce +import Utils + +import qualified App +import qualified Keyboard +import qualified Transforms +-------------------------------------------------------------------------------- + +main :: IO () +main = hspec $ do + describe "Keyboard.print" $ do + it "pretty-prints the keyboard" $ do + show Keyboard.qwerty == "[1][2][3][4][5][6][7][8][9][0]\n[Q][W][E][R][T][Y][U][I][O][P]\n[A][S][D][F][G][H][J][K][L][;]\n[Z][X][C][V][B][N][M][,][.][/]" + + describe "Transforms.fromString" $ do + it "successfully parses a string of commands" $ do + Transforms.fromString "HHVS-12VHVHS3" == + Just [ HorizontalFlip + , HorizontalFlip + , VerticalFlip + , Shift (-12) + , VerticalFlip + , HorizontalFlip + , VerticalFlip + , HorizontalFlip + , Shift 3 + ] + + it "returns Nothing when the input is invalid" $ do + Transforms.fromString "potato" == Nothing + + it "return Nothing when the input is valid except for the end" $ do + Transforms.fromString "HVS10potato" == Nothing + + describe "App.transform" $ do + it "flips any keyboard horizontally" $ do + property $ \first second third fourth -> + App.transform (Keyboard [first, second, third, fourth]) HorizontalFlip == do + Keyboard [ reverse first + , reverse second + , reverse third + , reverse fourth + ] + + it "flips any keyboard vertically" $ do + property $ \first second third fourth -> + App.transform (Keyboard [first, second, third, fourth]) VerticalFlip == do + Keyboard $ reverse [first, second, third, fourth] + + it "shifts any keyboard" $ do + property $ \first second third fourth n -> + App.transform (Keyboard [first, second, third, fourth]) (Shift n) + |> (coerce :: Keyboard -> [[Char]]) + |> concat == + [first, second, third, fourth] + |> concat + |> Utils.rotate n + + it "flips a QWERTY keyboard horizontally" $ do + App.transform Keyboard.qwerty HorizontalFlip == do + Keyboard [ ['0','9','8','7','6','5','4','3','2','1'] + , ['P','O','I','U','Y','T','R','E','W','Q'] + , [';','L','K','J','H','G','F','D','S','A'] + , ['/','.',',','M','N','B','V','C','X','Z'] + ] + + it "flips a keyboard vertically" $ do + App.transform Keyboard.qwerty VerticalFlip == do + Keyboard [ ['Z','X','C','V','B','N','M',',','.','/'] + , ['A','S','D','F','G','H','J','K','L',';'] + , ['Q','W','E','R','T','Y','U','I','O','P'] + , ['1','2','3','4','5','6','7','8','9','0'] + ] + + it "shifts a keyboard left N times" $ do + App.transform Keyboard.qwerty (Shift 2) == do + Keyboard [ ['3','4','5','6','7','8','9','0','Q','W'] + , ['E','R','T','Y','U','I','O','P','A','S'] + , ['D','F','G','H','J','K','L',';','Z','X'] + , ['C','V','B','N','M',',','.','/','1','2'] + ] + + it "shifts right negative amounts" $ do + App.transform Keyboard.qwerty (Shift (-3)) == do + Keyboard [ [',','.','/','1','2','3','4','5','6','7'] + , ['8','9','0','Q','W','E','R','T','Y','U'] + , ['I','O','P','A','S','D','F','G','H','J'] + , ['K','L',';','Z','X','C','V','B','N','M'] + ] + + describe "Transforms.optimize" $ do + it "removes superfluous horizontal transformations" $ do + Transforms.optimize [HorizontalFlip, HorizontalFlip] == [] + + it "removes superfluous vertical transformations" $ do + Transforms.optimize [VerticalFlip, VerticalFlip] == [] diff --git a/users/wpcarro/assessments/brilliant/Transforms.hs b/users/wpcarro/assessments/brilliant/Transforms.hs new file mode 100644 index 000000000..d8df8f837 --- /dev/null +++ b/users/wpcarro/assessments/brilliant/Transforms.hs @@ -0,0 +1,52 @@ +-------------------------------------------------------------------------------- +module Transforms where +-------------------------------------------------------------------------------- +import Control.Applicative ((<|>)) +import Text.ParserCombinators.ReadP +-------------------------------------------------------------------------------- + +data Transform = VerticalFlip + | HorizontalFlip + | Shift Int + deriving (Eq, Show) + +digit :: ReadP Char +digit = + satisfy (\c -> c >= '0' && c <= '9') + +command :: ReadP Transform +command = vertical + <|> horizontal + <|> shift + where + vertical = + char 'V' >> pure VerticalFlip + + horizontal = + char 'H' >> pure HorizontalFlip + + shift = do + _ <- char 'S' + negative <- option Nothing $ fmap Just (satisfy (== '-')) + n <- read <$> many1 digit + case negative of + Nothing -> pure $ Shift n + Just _ -> pure $ Shift (-1 * n) + +-- | Attempt to remove redundant transformations. +-- | Here are some rules that I'd like to support but may not have time for: +-- | - All even-numbered flips (w/o intermittent shifts) can become zero +-- | - All odd-numbered flips (w/o intermittent shifts) can become 1 +-- | - All shifts can be be reduce to the absolute value of shifts +optimize :: [Transform] -> [Transform] +optimize [] = [] +optimize [x] = [x] +optimize (VerticalFlip:VerticalFlip:xs) = optimize xs +optimize (HorizontalFlip:HorizontalFlip:xs) = optimize xs +optimize xs = xs + +fromString :: String -> Maybe [Transform] +fromString x = + case readP_to_S (manyTill command eof) x of + [(res, "")] -> Just res + _ -> Nothing diff --git a/users/wpcarro/assessments/brilliant/Utils.hs b/users/wpcarro/assessments/brilliant/Utils.hs new file mode 100644 index 000000000..c69d00333 --- /dev/null +++ b/users/wpcarro/assessments/brilliant/Utils.hs @@ -0,0 +1,13 @@ +-------------------------------------------------------------------------------- +module Utils where +-------------------------------------------------------------------------------- +import Data.Function ((&)) +-------------------------------------------------------------------------------- + +(|>) :: a -> (a -> b) -> b +(|>) = (&) + +-- | Rotate `xs` as a cycle `n` times. +rotate :: Int -> [a] -> [a] +rotate n xs = take size . drop (n `mod` size) . cycle $ xs + where size = length xs diff --git a/users/wpcarro/assessments/brilliant/default.nix b/users/wpcarro/assessments/brilliant/default.nix new file mode 100644 index 000000000..886ba87e1 --- /dev/null +++ b/users/wpcarro/assessments/brilliant/default.nix @@ -0,0 +1,16 @@ +let + briefcase = import {}; +in briefcase.buildHaskell.program { + name = "transform-keyboard"; + srcs = builtins.path { + path = ./.; + name = "transform-keyboard-src"; + }; + deps = hpkgs: with hpkgs; [ + optparse-applicative + unordered-containers + split + rio + ]; + ghcExtensions = []; +} diff --git a/users/wpcarro/assessments/brilliant/shell.nix b/users/wpcarro/assessments/brilliant/shell.nix new file mode 100644 index 000000000..d0a6c7e5e --- /dev/null +++ b/users/wpcarro/assessments/brilliant/shell.nix @@ -0,0 +1,16 @@ +let + pkgs = import (builtins.fetchGit { + url = "https://github.com/NixOS/nixpkgs-channels"; + ref = "nixos-20.03"; + rev = "afa9ca61924f05aacfe495a7ad0fd84709d236cc"; + }) {}; +in pkgs.mkShell { + buildInputs = with pkgs; [ + (haskellPackages.ghcWithPackages (hpkgs: with hpkgs; [ + hspec + optparse-applicative + unordered-containers + split + ])) + ]; +} diff --git a/users/wpcarro/assessments/dotted-squares/.envrc b/users/wpcarro/assessments/dotted-squares/.envrc new file mode 100644 index 000000000..a4a62da52 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/assessments/dotted-squares/.ghci b/users/wpcarro/assessments/dotted-squares/.ghci new file mode 100644 index 000000000..b100af443 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/.ghci @@ -0,0 +1 @@ +:set -Wall diff --git a/users/wpcarro/assessments/dotted-squares/Main.hs b/users/wpcarro/assessments/dotted-squares/Main.hs new file mode 100644 index 000000000..44f91e2b2 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/Main.hs @@ -0,0 +1,218 @@ +{-# LANGUAGE DeriveGeneric #-} +-------------------------------------------------------------------------------- +module Main where +-------------------------------------------------------------------------------- +import Data.Hashable +import Data.Function ((&)) +import GHC.Generics +import Text.ParserCombinators.ReadP +import Control.Applicative + +import qualified Data.HashSet as HS +-------------------------------------------------------------------------------- + +data Direction + = DirLeft + | DirRight + | DirUp + | DirDown + deriving (Eq, Show) + +data Point = Point Int Int + deriving (Eq, Show, Ord, Generic) +instance Hashable Point + +data Orientation + = Horizontal + | Vertical + deriving (Eq, Show) + +data Anchor + = Beg + | End + deriving (Eq, Show) + +data Rotation + = CW + | CCW + deriving (Eq, Show) + +data Line = Line Point Point + deriving (Show, Generic) +instance Hashable Line + +instance Eq Line where + Line begA endA == Line begB endB = + (begA == begB && endA == endB) || + (begA == endB && endA == begB) + +data Game = Game (HS.HashSet Line) [Line] + deriving (Eq, Show) + +data Scoreboard = Scoreboard Int Int + deriving (Eq) + +instance Semigroup Scoreboard where + (Scoreboard a b) <> (Scoreboard x y) = + Scoreboard (a + x) (b + y) + +instance Monoid Scoreboard where + mempty = Scoreboard 0 0 + +data Turn + = Player1 + | Player2 + deriving (Eq, Show) + +next :: Turn -> Turn +next Player1 = Player2 +next Player2 = Player1 + +instance Show Scoreboard where + show (Scoreboard p1 p2) = + "Player 1: " ++ show (p1) ++ " Player 2: " ++ show (p2) + +digit :: ReadP Char +digit = satisfy (\c -> c >= '0' && c <= '9') + +int :: ReadP Int +int = read <$> many1 digit + +inputLine :: ReadP String +inputLine = manyTill get (char '\n') + +direction :: ReadP Direction +direction = do + c <- char 'L' <|> char 'R' <|> char 'U' <|> char 'D' + case c of + 'L' -> pure DirLeft + 'R' -> pure DirRight + 'U' -> pure DirUp + 'D' -> pure DirDown + _ -> fail $ "Unexpected direction: " ++ show c + +validMove :: Int -> Int -> ReadP Line +validMove w h = do + x <- int + skipSpaces + y <- int + skipSpaces + dir <- direction + _ <- char '\n' + if x >= 0 && x <= w && y >= 0 && y <= h then do + let beg = Point x y + pure $ mkLine beg (shiftPoint dir beg) + else + fail "Expected a move on the game board" + +game :: ReadP Game +game = do + w <- read <$> inputLine + h <- read <$> inputLine + locs <- read <$> inputLine + moves <- count locs (validMove w h) + eof + pure $ Game mempty moves + +parseInput :: String -> Maybe Game +parseInput x = do + case readP_to_S game x of + [(res, "")] -> Just res + _ -> Nothing + +-- | Smart constructor to ensure that beg is always < end. +mkLine :: Point -> Point -> Line +mkLine beg end = + if beg < end then Line beg end else Line end beg + +mkLineDir :: Int -> Int -> Direction -> Line +mkLineDir x y dir = + let beg = Point x y + in mkLine beg (shiftPoint dir beg) + +mkLineDir' :: Point -> Direction -> Line +mkLineDir' (Point x y) dir = mkLineDir x y dir + +shiftPoint :: Direction -> Point -> Point +shiftPoint DirLeft (Point x y) = Point (x - 1) y +shiftPoint DirRight (Point x y) = Point (x + 1) y +shiftPoint DirUp (Point x y) = Point x (y + 1) +shiftPoint DirDown (Point x y) = Point x (y - 1) + +shiftLine :: Direction -> Line -> Line +shiftLine dir (Line beg end) = + mkLine (shiftPoint dir beg) (shiftPoint dir end) + +rotateLine :: Anchor -> Rotation -> Line -> Line +rotateLine anchor rotation line = + doRotateLine (classifyOrientation line) anchor rotation line + +doRotateLine :: Orientation -> Anchor -> Rotation -> Line -> Line +doRotateLine Horizontal Beg CW (Line beg _) = mkLineDir' beg DirDown +doRotateLine Horizontal Beg CCW (Line beg _) = mkLineDir' beg DirUp +doRotateLine Horizontal End CW (Line _ end) = mkLineDir' end DirUp +doRotateLine Horizontal End CCW (Line _ end) = mkLineDir' end DirDown +doRotateLine Vertical Beg CW (Line beg _) = mkLineDir' beg DirRight +doRotateLine Vertical Beg CCW (Line beg _) = mkLineDir' beg DirLeft +doRotateLine Vertical End CW (Line _ end) = mkLineDir' end DirLeft +doRotateLine Vertical End CCW (Line _ end) = mkLineDir' end DirRight + +classifyOrientation :: Line -> Orientation +classifyOrientation (Line (Point _ y1) (Point _ y2)) = + if y1 == y2 then Horizontal else Vertical + +closesAnySquare :: HS.HashSet Line -> Line -> Bool +closesAnySquare allMoves line = do + let alreadyDrawn x = HS.member x allMoves + case classifyOrientation line of + Horizontal -> + all alreadyDrawn + [ shiftLine DirUp line + , rotateLine Beg CCW line + , rotateLine End CW line + ] || + all alreadyDrawn + [ shiftLine DirDown line + , rotateLine Beg CW line + , rotateLine End CCW line + ] + Vertical -> + all alreadyDrawn + [ shiftLine DirLeft line + , rotateLine Beg CCW line + , rotateLine End CW line + ] || + all alreadyDrawn + [ shiftLine DirRight line + , rotateLine Beg CW line + , rotateLine End CCW line + ] + +incScoreboard :: Turn -> Scoreboard -> Scoreboard +incScoreboard Player1 score = score <> Scoreboard 1 0 +incScoreboard Player2 score = score <> Scoreboard 0 1 + +scoreGame :: Turn -> Game -> Scoreboard -> Maybe Scoreboard +scoreGame _ (Game _ []) score = Just $ score +scoreGame player (Game allMoves (line:rest)) score = + if HS.member line allMoves then + Nothing + else do + let allMoves' = HS.insert line allMoves + score' = if closesAnySquare allMoves line then + incScoreboard player score + else score + scoreGame (next player) (Game allMoves' rest) score' + +(|>) :: a -> (a -> b) -> b +(|>) = (&) + +main :: IO () +main = do + input <- readFile "game.txt" + case parseInput input of + Nothing -> putStrLn "invalid" + Just parsedGame -> + case scoreGame Player1 parsedGame mempty of + Nothing -> putStrLn "invalid" + Just score -> print score diff --git a/users/wpcarro/assessments/dotted-squares/README.md b/users/wpcarro/assessments/dotted-squares/README.md new file mode 100644 index 000000000..3d13da1cb --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/README.md @@ -0,0 +1,21 @@ +# Dotted Squares + +This is my second attempt at solving this problem. I had an hour to solve it the +first time, and I unfortunately came up short although I made good progress. + +The problem asks to read input from a text file that looks like this: + +``` +1 -- board width +1 -- board height +4 -- number of lines of "moves" (below) +0 0 R -- create a unit vector (0,0) facing right +0 0 U -- create a unit vector (0,0) facing up +0 1 L -- create a unit vector (0,1) facing left +1 1 D -- create a unit vector (1,1) facing down +``` + +After parsing and validating the input, score the outcome a game where players +one and two alternatively take turns drawing lines on a board. Anytime one of +the players draws a line that creates a square from existing lines, they get a +point. diff --git a/users/wpcarro/assessments/dotted-squares/Spec.hs b/users/wpcarro/assessments/dotted-squares/Spec.hs new file mode 100644 index 000000000..b5d604085 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/Spec.hs @@ -0,0 +1,80 @@ +-------------------------------------------------------------------------------- +module Spec where +-------------------------------------------------------------------------------- +import Test.Hspec +import Main hiding (main) +import qualified Data.HashSet as HS +-------------------------------------------------------------------------------- + +main :: IO () +main = hspec $ do + describe "dotted-squares" $ do + describe "parseInput" $ do + it "works as expected" $ do + input <- readFile "input-a.txt" + parseInput input `shouldBe` Just (Game mempty [ mkLine (Point 0 0) (Point 1 0) + , mkLine (Point 0 0) (Point 0 1) + ]) + + it "fails when the game has too many user moves" $ do + input <- readFile "too-many-moves.txt" + parseInput input `shouldBe` Nothing + + it "fails when the game has too few user moves" $ do + input <- readFile "too-few-moves.txt" + parseInput input `shouldBe` Nothing + + describe "shiftLine" $ do + let horizontal = mkLineDir 1 1 DirRight + vertical = mkLineDir 1 1 DirUp + it "can move a horizontal line up" $ + shiftLine DirUp horizontal `shouldBe` mkLineDir 1 2 DirRight + it "can move a horizontal line down" $ + shiftLine DirDown horizontal `shouldBe` mkLineDir 1 0 DirRight + it "can move a horizontal line left" $ + shiftLine DirLeft horizontal `shouldBe` mkLineDir 0 1 DirRight + it "can move a horizontal line right" $ + shiftLine DirRight horizontal `shouldBe` mkLineDir 2 1 DirRight + it "can move a vertical line up" $ + shiftLine DirUp vertical `shouldBe` mkLineDir 1 2 DirUp + it "can move a vertical line down" $ + shiftLine DirDown vertical `shouldBe` mkLineDir 1 0 DirUp + it "can move a vertical line left" $ + shiftLine DirLeft vertical `shouldBe` mkLineDir 0 1 DirUp + it "can move a vertical line right" $ + shiftLine DirRight vertical `shouldBe` mkLineDir 2 1 DirUp + + describe "rotateLine" $ do + let horizontal = mkLineDir 1 1 DirRight -- 1,1;2,1 + vertical = mkLineDir 1 1 DirUp -- 1,1;1,2 + it "can rotate a horizontal line CW anchored at its beginning" $ + rotateLine Beg CW horizontal `shouldBe` mkLineDir 1 1 DirDown + it "can rotate a horizontal line CCW anchored at its beginning" $ + rotateLine Beg CCW horizontal `shouldBe` mkLineDir 1 1 DirUp + it "can rotate a horizontal line CW anchored at its end" $ + rotateLine End CW horizontal `shouldBe` mkLineDir 2 1 DirUp + it "can rotate a horizontal line CCW anchored at its end" $ + rotateLine End CCW horizontal `shouldBe` mkLineDir 2 1 DirDown + + it "can rotate a vertical line CW anchored at its beginning" $ + rotateLine Beg CW vertical `shouldBe` mkLineDir 1 1 DirRight + it "can rotate a vertical line CCW anchored at its beginning" $ + rotateLine Beg CCW vertical `shouldBe` mkLineDir 1 1 DirLeft + it "can rotate a vertical line CW anchored at its end" $ + rotateLine End CW vertical `shouldBe` mkLineDir 1 2 DirLeft + it "can rotate a vertical line CCW anchored at its end" $ + rotateLine End CCW vertical `shouldBe` mkLineDir 1 2 DirRight + + describe "closesAnySquare" $ do + let threeSides = [ (0, 0, DirRight) + , (0, 0, DirUp) + , (0, 1, DirRight) + ] + |> fmap (\(x, y, dir) -> mkLineDir x y dir) + |> HS.fromList + it "returns true the line we supply makes a square" $ + closesAnySquare threeSides (mkLineDir 1 1 DirDown) `shouldBe` True + it "returns false the line we supply doesn't make a square" $ + closesAnySquare threeSides (mkLineDir 1 1 DirUp) `shouldBe` False + it "returns false when we have no existing lines" $ + closesAnySquare mempty (mkLineDir 1 1 DirUp) `shouldBe` False diff --git a/users/wpcarro/assessments/dotted-squares/colliding-moves.txt b/users/wpcarro/assessments/dotted-squares/colliding-moves.txt new file mode 100644 index 000000000..a831fa95c --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/colliding-moves.txt @@ -0,0 +1,7 @@ +1 +1 +4 +0 0 R +0 0 R +0 1 R +0 1 R diff --git a/users/wpcarro/assessments/dotted-squares/game.txt b/users/wpcarro/assessments/dotted-squares/game.txt new file mode 100644 index 000000000..0af71d1f5 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/game.txt @@ -0,0 +1,7 @@ +1 +1 +4 +0 0 R +0 0 U +0 1 R +1 1 D diff --git a/users/wpcarro/assessments/dotted-squares/input-a.txt b/users/wpcarro/assessments/dotted-squares/input-a.txt new file mode 100644 index 000000000..b9e871ece --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/input-a.txt @@ -0,0 +1,5 @@ +1 +1 +2 +0 0 R +0 0 U diff --git a/users/wpcarro/assessments/dotted-squares/shell.nix b/users/wpcarro/assessments/dotted-squares/shell.nix new file mode 100644 index 000000000..87eb23d73 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/shell.nix @@ -0,0 +1,8 @@ +let + briefcase = import {}; +in briefcase.buildHaskell.shell { + deps = hpkgs: with hpkgs; [ + hspec + unordered-containers + ]; +} diff --git a/users/wpcarro/assessments/dotted-squares/too-few-moves.txt b/users/wpcarro/assessments/dotted-squares/too-few-moves.txt new file mode 100644 index 000000000..d684679d2 --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/too-few-moves.txt @@ -0,0 +1,6 @@ +1 +1 +4 +0 0 R +0 0 U +0 1 R diff --git a/users/wpcarro/assessments/dotted-squares/too-many-moves.txt b/users/wpcarro/assessments/dotted-squares/too-many-moves.txt new file mode 100644 index 000000000..bfcced43b --- /dev/null +++ b/users/wpcarro/assessments/dotted-squares/too-many-moves.txt @@ -0,0 +1,7 @@ +1 +1 +3 +0 0 R +0 0 U +0 1 R +1 1 D diff --git a/users/wpcarro/assessments/ramp/solution-emacs-elixir-format.py b/users/wpcarro/assessments/ramp/solution-emacs-elixir-format.py new file mode 100644 index 000000000..d0d948402 --- /dev/null +++ b/users/wpcarro/assessments/ramp/solution-emacs-elixir-format.py @@ -0,0 +1,29 @@ +# The file '2010.census.txt' contains summary statistics from the 2010 United +# States census including household income. The data is in an unspecified +# format. + +# Find the average of the column called: + +# 'MEDIAN HOUSEHOLD INCOME' + +# Ideally the solution should be a command line script, of the form: + +# $ ./solution [options] [file...] + +# The solution may be written in any language, Python is preferred but not +# required. + +# Google, stack overflow, etc. usage is allowed. + +import requests + +url = "https://assets.tryramp.com/interview/census/2010.census.txt" + +def main(): + res = requests.get(url) + if res.status not in {200}: + raise Exception("Unexpected status code: {}".format(res.status_code)) + # download the content + # parse row + # select 'MEDIAN HOUSEHOLD INCOME' column + pass diff --git a/users/wpcarro/assessments/ramp/solution.py b/users/wpcarro/assessments/ramp/solution.py new file mode 100644 index 000000000..28060bfb3 --- /dev/null +++ b/users/wpcarro/assessments/ramp/solution.py @@ -0,0 +1,87 @@ +# The file '2010.census.txt' contains summary statistics from the 2010 United +# States census including household income. The data is in an unspecified +# format. + +# Find the average of the column called: + +# 'MEDIAN HOUSEHOLD INCOME' + +# Ideally the solution should be a command line script, of the form: + +# $ ./solution [options] [file...] + +# The solution may be written in any language, Python is preferred but not +# required. + +# Google, stack overflow, etc. usage is allowed. + +import requests +import csv + +url = "https://assets.tryramp.com/interview/census/2010.census.txt" +column = 'MEDIAN HOUSEHOLD INCOME' +columns = [ + 'CENSUS YEAR', + 'TRACT', + 'BLOCK GROUP', + 'FIPS ID', + 'TOTAL POPULATION', + 'POPULATION WHITE', + 'POPULATION BLACK', + 'POPULATION ASIAN', + 'POPULATION OTHER', + 'POPULATION AMERICAN INDIAN', + 'POPULATION PACIFIC ISLANDER', + 'POPULATION ONE RACE', + 'POPULATION MULTI RACE', + 'POPULATION 25 OLDER', + 'MEDIAN AGE', + 'MEDIAN HOUSEHOLD INCOME', + 'HIGH SCHOOL MALE', + 'HIGH SCHOOL MORE MALE', + 'COLLEGE 1 YR LESS MALE', + 'COLLEGE 1 YR MORE MALE', + 'ASSOCIATES DEGREE MALE', + 'BACHELORS DEGREE MALE', + 'MASTERS DEGREE MALE', + 'PROFESSIONAL DEGREE MALE', + 'DOCTORAL DEGREE MALE', + 'HIGH SCHOOL FEMALE', + 'HIGH SCHOOL MORE FEMALE', + 'COLLEGE 1 YR LESS FEMALE', + 'COLLEGE 1 YR MORE FEMALE', + 'ASSOCIATES DEGREE FEMALE', + 'BACHELORS DEGREE FEMALE', + 'MASTERS DEGREE FEMALE', + 'PROFESSIONAL DEGREE FEMALE', + 'DOCTORAL DEGREE FEMALE', + 'PERCENT 25 YR OVER HIGH SCHOOL MORE', + 'HOUSING UNITS', + 'OCCUPIED HOUSING UNITS', + 'OWNER OCCUPIED HOUSING', + 'RENTER OCCUPIED HOUSING', + 'PERCENT OWNER OCCUPIED', + 'PERCENT RENTER OCCUPIED', + 'MEDIAN HOUSE VALUE OWNER OCCUPIED', + 'MEDIAN YEAR BUILT', + 'VACANCY RATES', +] + + +def average(xs): + return sum(xs) / len(xs) + + +def parse_body(body): + return list(csv.DictReader(body.split('\n')[1:], delimiter='|', fieldnames=columns)) + + +def main(): + res = requests.get(url) + if res.status_code not in {200}: + raise Exception("Unexpected status code: {}".format(res.status_code)) + return average([int(d.get(column)) + for d in parse_body(res.text) + if int(d.get(column)) >= 0]) + +print(main()) diff --git a/users/wpcarro/assessments/semiprimes/.gitignore b/users/wpcarro/assessments/semiprimes/.gitignore new file mode 100644 index 000000000..b5b25bd64 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/.gitignore @@ -0,0 +1 @@ +default.nix diff --git a/users/wpcarro/assessments/semiprimes/README.md b/users/wpcarro/assessments/semiprimes/README.md new file mode 100644 index 000000000..7d5a15482 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/README.md @@ -0,0 +1,44 @@ +# Semiprimes Service + +## Introduction + +A **composite** is a number containing at least two prime factors. For example: + +``` +15 = 3 × 5 +9 = 3 × 3 +12 = 2 × 2 × 3 +``` + +There are ten composites below thirty containing precisely two, not necessarily +distinct, prime factors: `4, 6, 9, 10, 14, 15, 21, 22, 25, 26`. Let’s call such +numbers *Semiprimes*. + +## Task + +- Write a module which provides a function to tell whether a given number, `N`, + is a semiprime. `N` will be less than 100,000 +- Please implement an API (RESTful or GraphQL) to factor a given number into two + prime numbers if it’s a semiprime, otherwise, return an error message. + +## Stretch Goals + +- Handle the invalid inputs. +- Support batch requests: i.e. users could provide 100 numbers, and the API + return the answer for all. +- Considering this module will be used by a long running service, could you + optimize it to give answers faster? + +## Usage + +To run the application you'll need to have `elixir` installed. Assuming `elixir` +is already installed, consult the following steps to start the application: + +```shell +$ cd server +$ mix deps.get +$ iex -S mix +``` + +Now open a web browser and visit `http://localhost:8080`! + diff --git a/users/wpcarro/assessments/semiprimes/default.nix b/users/wpcarro/assessments/semiprimes/default.nix new file mode 100644 index 000000000..29452eac2 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/default.nix @@ -0,0 +1 @@ +# stubbed diff --git a/users/wpcarro/assessments/semiprimes/server/.formatter.exs b/users/wpcarro/assessments/semiprimes/server/.formatter.exs new file mode 100644 index 000000000..d2cda26ed --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/users/wpcarro/assessments/semiprimes/server/.gitignore b/users/wpcarro/assessments/semiprimes/server/.gitignore new file mode 100644 index 000000000..db9704a85 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +server-*.tar + diff --git a/users/wpcarro/assessments/semiprimes/server/lib/app.ex b/users/wpcarro/assessments/semiprimes/server/lib/app.ex new file mode 100644 index 000000000..7a6fa5ea2 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/app.ex @@ -0,0 +1,8 @@ +defmodule App do + use Application + + @impl true + def start(_type, _args) do + Sup.start_link() + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/lib/cache.ex b/users/wpcarro/assessments/semiprimes/server/lib/cache.ex new file mode 100644 index 000000000..cd064cc1a --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/cache.ex @@ -0,0 +1,41 @@ +defmodule Cache do + @moduledoc """ + Cache is an in-memory key-value store. + """ + use Agent + + @doc """ + Inititalize the key-value store. + """ + def start_link(_) do + Agent.start_link(fn -> %{} end, name: __MODULE__) + end + + @doc """ + Attempt to return the value stored at `key` + """ + def get(key) do + Agent.get(__MODULE__, &Map.get(&1, key)) + end + + @doc """ + Write the `value` under the `key`. Last writer wins. + """ + def put(key, value) do + Agent.update(__MODULE__, &Map.put(&1, key, value)) + end + + @doc """ + List the contents of the cache. Useful for debugging purposes. + """ + def list() do + Agent.get(__MODULE__, & &1) + end + + @doc """ + Invalidate the entire cache. + """ + def clear() do + Agent.update(__MODULE__, fn _ -> %{} end) + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/lib/extras.ex b/users/wpcarro/assessments/semiprimes/server/lib/extras.ex new file mode 100644 index 000000000..f0c2ea4b9 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/extras.ex @@ -0,0 +1,22 @@ +defmodule Extras do + @moduledoc """ + Hosts utility functions intended to supplement the standard library. + """ + + @doc """ + Return an ascending range starting at `a` and ending at `b` (exclusive). + + ## Examples + + iex> Extras.range(2, 5) + [2, 3, 4] + + """ + def range(a, b) do + if b <= a do + [] + else + [a] ++ range(a + 1, b) + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/lib/math.ex b/users/wpcarro/assessments/semiprimes/server/lib/math.ex new file mode 100644 index 000000000..8a33be475 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/math.ex @@ -0,0 +1,26 @@ +defmodule Math do + @moduledoc """ + Math utilities. + """ + alias Extras + + @doc """ + Returns the prime factors for `n`. + + ## Examples + + iex> Math.factor(15) + [3, 5] + + """ + def factor(1), do: [] + + def factor(n) do + Extras.range(2, n - 1) + |> Enum.find(&(rem(n, &1) == 0)) + |> case do + nil -> [n] + x -> [x | factor(div(n, x))] + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/lib/router.ex b/users/wpcarro/assessments/semiprimes/server/lib/router.ex new file mode 100644 index 000000000..cb5552092 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/router.ex @@ -0,0 +1,86 @@ +defmodule Router do + use Plug.Router + use Plug.Debugger + require Logger + + plug(Plug.Logger, log: :debug) + plug(Plug.Parsers, parsers: [:urlencoded]) + plug(:match) + plug(:dispatch) + + @usage """ + Usage: Try querying some of the following endpoints... + GET / + GET /help + GET /semiprime?number= + GET /semiprimes?numbers= + """ + + get "/" do + send_resp(conn, 200, "Welcome to Semiprimes Service!\n\n#{@usage}") + end + + get "/help" do + send_resp(conn, 200, @usage) + end + + get "/semiprime" do + case conn |> Map.get(:query_params) |> Map.get("number") do + nil -> + send_resp(conn, 400, "You must pass an integer as a query parameter. #{@usage}") + + val -> + case Integer.parse(val) do + {n, ""} -> + send_resp(conn, 200, semiprime_response(n)) + + _ -> + send_resp(conn, 400, "We could not parse the number you provided.\n\n#{@usage}") + end + end + end + + get "/semiprimes" do + case conn |> Map.get(:query_params) |> Map.get("numbers") do + nil -> + send_resp( + conn, + 400, + "You must pass a comma-separated list of integers as a query parameter.\n\n#{@usage}" + ) + + xs -> + response = + xs + |> String.split(",") + |> Stream.map(&Integer.parse/1) + |> Stream.filter(fn + {n, ""} -> true + _ -> false + end) + |> Stream.map(fn {n, ""} -> semiprime_response(n) end) + |> Enum.join("\n") + + send_resp(conn, 200, response) + end + end + + match _ do + send_resp(conn, 404, "Not found.") + end + + ################################################################################ + # Utils + ################################################################################ + + defp semiprime_response(n) do + case Server.semiprime(n) do + nil -> + "#{n} is not a semiprime. Try another number!" + + {hit_or_miss, factors} -> + response = "#{n} is a semiprime! Its factors are #{Enum.join(factors, " and ")}." + "Cache #{Atom.to_string(hit_or_miss)} - #{response}" + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/lib/server.ex b/users/wpcarro/assessments/semiprimes/server/lib/server.ex new file mode 100644 index 000000000..7ab5e905b --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/server.ex @@ -0,0 +1,33 @@ +defmodule Server do + @moduledoc """ + Documentation for `Server`. + """ + + @doc """ + If `n` contains exactly two prime factors, return those prime factors; + otherwise, return nothing. + """ + def semiprime(n) do + case Cache.get(n) do + nil -> + case do_semiprime(n) do + nil -> + nil + + res -> + Cache.put(n, res) + {:miss, res} + end + + hit -> + {:hit, hit} + end + end + + defp do_semiprime(n) do + case Math.factor(n) do + [_, _] = res -> res + _ -> nil + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/lib/sup.ex b/users/wpcarro/assessments/semiprimes/server/lib/sup.ex new file mode 100644 index 000000000..13a6ab374 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/lib/sup.ex @@ -0,0 +1,23 @@ +defmodule Sup do + @moduledoc """ + Top-level supervisor for our OTP application. For now, this supervisor starts + and monitors our cache. + """ + + use Supervisor + alias Plug.Adapters.Cowboy + + def start_link(opts \\ []) do + Supervisor.start_link(__MODULE__, :ok, opts) + end + + @impl true + def init(:ok) do + children = [ + Cache, + Cowboy.child_spec(scheme: :http, plug: Router, options: [port: 8000]) + ] + + Supervisor.init(children, strategy: :one_for_one) + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/mix.exs b/users/wpcarro/assessments/semiprimes/server/mix.exs new file mode 100644 index 000000000..9062f927e --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/mix.exs @@ -0,0 +1,32 @@ +defmodule Server.MixProject do + use Mix.Project + + def project do + [ + app: :server, + version: "0.1.0", + elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {App, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:cortex, "~> 0.1", only: [:dev, :test]}, + {:plug_cowboy, "~> 2.4.1"}, + {:cowboy, "~> 2.8.0"}, + {:plug, "~> 1.11.0"}, + {:poison, "~> 4.0.1"} + ] + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/mix.lock b/users/wpcarro/assessments/semiprimes/server/mix.lock new file mode 100644 index 000000000..2ae7efbb3 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/mix.lock @@ -0,0 +1,14 @@ +%{ + "cortex": {:hex, :cortex, "0.6.0", "8094830fae266eb0ae34d1a58983c0c49484341f5044fb4dfb81746647bd2993", [:mix], [{:file_system, "~> 0.2", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "d0ef5a2b1269626149118684dc4ea77dbfbc67017f4b4065b71dcefa26cfcc49"}, + "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, + "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"}, + "plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, + "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, + "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, + "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, +} diff --git a/users/wpcarro/assessments/semiprimes/server/test/extras_test.exs b/users/wpcarro/assessments/semiprimes/server/test/extras_test.exs new file mode 100644 index 000000000..67d0b8875 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/test/extras_test.exs @@ -0,0 +1,18 @@ +defmodule ExtrasTest do + use ExUnit.Case + doctest Extras + + describe "range" do + test "returns an empty list for descending sequences" do + assert Extras.range(0, -2) == [] + end + + test "returns an empty list for non-ascending sequences" do + assert Extras.range(8, 8) == [] + end + + test "returns an exclusive range" do + assert Extras.range(3, 6) == [3, 4, 5] + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/test/math_test.exs b/users/wpcarro/assessments/semiprimes/server/test/math_test.exs new file mode 100644 index 000000000..c7186c824 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/test/math_test.exs @@ -0,0 +1,30 @@ +defmodule MathTest do + use ExUnit.Case + doctest Math + + describe "factor" do + test "returns the prime factors for an input" do + [ + {15, [3, 5]}, + {12, [2, 2, 3]}, + {9, [3, 3]}, + {21, [3, 7]} + ] + |> Enum.map(fn {input, expected} -> + assert Math.factor(input) == expected + end) + end + + test "handles large numbers" do + assert Math.factor(104_023) == [17, 29, 211] + end + + test "returns an empty list for 1" do + assert Math.factor(1) == [] + end + + test "returns the prime number itself when the input is prime" do + assert Math.factor(7) == [7] + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/test/server_test.exs b/users/wpcarro/assessments/semiprimes/server/test/server_test.exs new file mode 100644 index 000000000..08d559734 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/test/server_test.exs @@ -0,0 +1,34 @@ +defmodule ServerTest do + use ExUnit.Case + doctest Server + + describe "semiprime" do + test "returns the factors when the number is semiprime" do + Cache.clear() + # Semiprimes below 30 + [ + {4, [2, 2]}, + {6, [2, 3]}, + {9, [3, 3]}, + {10, [2, 5]}, + {14, [2, 7]}, + {15, [3, 5]}, + {21, [3, 7]}, + {22, [2, 11]}, + {25, [5, 5]}, + {26, [2, 13]} + ] + |> Enum.each(fn {input, expected} -> + assert Server.semiprime(input) == {:miss, expected} + end) + end + + test "returns nothing when the number is a composite number" do + # Composite numbers below 30 + [1, 2, 3, 5, 7, 8, 11, 12, 13, 16, 17, 18, 19, 20, 23, 24, 27, 28, 29] + |> Enum.each(fn x -> + assert Server.semiprime(x) == nil + end) + end + end +end diff --git a/users/wpcarro/assessments/semiprimes/server/test/test_helper.exs b/users/wpcarro/assessments/semiprimes/server/test/test_helper.exs new file mode 100644 index 000000000..869559e70 --- /dev/null +++ b/users/wpcarro/assessments/semiprimes/server/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/users/wpcarro/assessments/tt/.gitignore b/users/wpcarro/assessments/tt/.gitignore new file mode 100644 index 000000000..d4d62d436 --- /dev/null +++ b/users/wpcarro/assessments/tt/.gitignore @@ -0,0 +1,6 @@ +.envrc +*.db +*.sqlite3 +!populate.sqlite3 +*.db-shm +*.db-wal \ No newline at end of file diff --git a/users/wpcarro/assessments/tt/README.md b/users/wpcarro/assessments/tt/README.md new file mode 100644 index 000000000..0231ef3ab --- /dev/null +++ b/users/wpcarro/assessments/tt/README.md @@ -0,0 +1,50 @@ +# TT + +All of the commands defined herein should be run from the top-level directory of +this repository (i.e. the directory in which this file exists). + +## Server + +To create the environment that contains all of this application's dependencies, +run: + +```shell +$ nix-shell +``` + +To run the server interactively, run: + +```shell +$ cd src/ +$ ghci +``` + +Now compile and load the server with: + +``` +Prelude> :l Main.hs +*Main> main +``` + +## Database + +Create a new database named `db.sqlite3` with: + +```shell +$ sqlite3 db.sqlite3 +``` + +Populate the database with: + +``` +sqlite3> .read populate.sqlite3 +``` + +You can verify that everything is setup with: + +``` +sqlite3> .tables +sqlite3> .schema +sqlite3> SELECT * FROM Accounts; +sqlite3> SELECT * FROM Trips; +``` diff --git a/users/wpcarro/assessments/tt/client/.gitignore b/users/wpcarro/assessments/tt/client/.gitignore new file mode 100644 index 000000000..1cb4f3034 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/.gitignore @@ -0,0 +1,3 @@ +/elm-stuff +/Main.min.js +/output.css diff --git a/users/wpcarro/assessments/tt/client/README.md b/users/wpcarro/assessments/tt/client/README.md new file mode 100644 index 000000000..04804ad94 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/README.md @@ -0,0 +1,18 @@ +# Elm + +Elm has one of the best developer experiences that I'm aware of. The error +messages are helpful and the entire experience is optimized to improve the ease +of writing web applications. + +## Developing + +If you're interested in contributing, the following will create an environment +in which you can develop: + +```shell +$ nix-shell +$ npx tailwindcss build index.css -o output.css +$ elm-live -- src/Main.elm --output=Main.min.js +``` + +You can now view your web client at `http://localhost:8000`! diff --git a/users/wpcarro/assessments/tt/client/dir-locals.nix b/users/wpcarro/assessments/tt/client/dir-locals.nix new file mode 100644 index 000000000..5c3ae0887 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/dir-locals.nix @@ -0,0 +1,3 @@ +let + briefcase = import /home/wpcarro/briefcase {}; +in briefcase.utils.nixBufferFromShell ./shell.nix diff --git a/users/wpcarro/assessments/tt/client/elm.json b/users/wpcarro/assessments/tt/client/elm.json new file mode 100644 index 000000000..c4095e118 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/elm.json @@ -0,0 +1,40 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "CurrySoftware/elm-datepicker": "4.0.0", + "elm/browser": "1.0.2", + "elm/core": "1.0.5", + "elm/html": "1.0.0", + "elm/http": "2.0.0", + "elm/json": "1.1.3", + "elm/random": "1.0.0", + "elm/svg": "1.0.1", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm-community/json-extra": "4.2.0", + "elm-community/list-extra": "8.2.3", + "elm-community/maybe-extra": "5.2.0", + "elm-community/random-extra": "3.1.0", + "justinmimbs/date": "3.2.1", + "krisajenkins/remotedata": "6.0.1", + "ryannhg/date-format": "2.3.0" + }, + "indirect": { + "elm/bytes": "1.0.8", + "elm/file": "1.0.5", + "elm/parser": "1.1.0", + "elm/virtual-dom": "1.0.2", + "owanturist/elm-union-find": "1.0.0", + "rtfeldman/elm-iso8601-date-strings": "1.1.3" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/users/wpcarro/assessments/tt/client/index.css b/users/wpcarro/assessments/tt/client/index.css new file mode 100644 index 000000000..52114e0e9 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/index.css @@ -0,0 +1,142 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +.elm-datepicker--container { + position: relative; +} + +.elm-datepicker--input:focus { + outline: 0; +} + +.elm-datepicker--picker { + position: absolute; + border: 1px solid #CCC; + z-index: 10; + background-color: white; +} + +.elm-datepicker--picker-header, +.elm-datepicker--weekdays { + background: #F2F2F2; +} + +.elm-datepicker--picker-header { + display: flex; + align-items: center; +} + +.elm-datepicker--prev-container, +.elm-datepicker--next-container { + flex: 0 1 auto; + cursor: pointer; +} + +.elm-datepicker--month-container { + flex: 1 1 auto; + padding: 0.5em; + display: flex; + flex-direction: column; +} + +.elm-datepicker--month, +.elm-datepicker--year { + flex: 1 1 auto; + cursor: default; + text-align: center; +} + +.elm-datepicker--year { + font-size: 0.6em; + font-weight: 700; +} + +.elm-datepicker--prev, +.elm-datepicker--next { + border: 6px solid transparent; + background-color: inherit; + display: block; + width: 0; + height: 0; + padding: 0 0.2em; +} + +.elm-datepicker--prev { + border-right-color: #AAA; +} + +.elm-datepicker--prev:hover { + border-right-color: #BBB; +} + +.elm-datepicker--next { + border-left-color: #AAA; +} + +.elm-datepicker--next:hover { + border-left-color: #BBB; +} + +.elm-datepicker--table { + border-spacing: 0; + border-collapse: collapse; + font-size: 0.8em; +} + +.elm-datepicker--table td { + width: 2em; + height: 2em; + text-align: center; +} + +.elm-datepicker--row { + border-top: 1px solid #F2F2F2; +} + +.elm-datepicker--dow { + border-bottom: 1px solid #CCC; + cursor: default; +} + +.elm-datepicker--day { + cursor: pointer; +} + +.elm-datepicker--day:hover { + background: #F2F2F2; +} + +.elm-datepicker--disabled { + cursor: default; + color: #DDD; +} + +.elm-datepicker--disabled:hover { + background: inherit; +} + +.elm-datepicker--picked { + color: white; + background: darkblue; +} + +.elm-datepicker--picked:hover { + background: darkblue; +} + +.elm-datepicker--today { + font-weight: bold; +} + +.elm-datepicker--other-month { + color: #AAA; +} + +.elm-datepicker--other-month.elm-datepicker--disabled { + color: #EEE; +} + +.elm-datepicker--other-month.elm-datepicker--picked { + color: white; +} diff --git a/users/wpcarro/assessments/tt/client/index.html b/users/wpcarro/assessments/tt/client/index.html new file mode 100644 index 000000000..9e6cef70d --- /dev/null +++ b/users/wpcarro/assessments/tt/client/index.html @@ -0,0 +1,38 @@ + + + + + + Elm SPA + + + + + + +
+ + + diff --git a/users/wpcarro/assessments/tt/client/print.css b/users/wpcarro/assessments/tt/client/print.css new file mode 100644 index 000000000..3cfb27923 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/print.css @@ -0,0 +1,3 @@ +.no-print { + display: none; +} diff --git a/users/wpcarro/assessments/tt/client/shell.nix b/users/wpcarro/assessments/tt/client/shell.nix new file mode 100644 index 000000000..15ac040b9 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/shell.nix @@ -0,0 +1,10 @@ +let + pkgs = import {}; +in pkgs.mkShell { + buildInputs = with pkgs; [ + nodejs + elmPackages.elm + elmPackages.elm-format + elmPackages.elm-live + ]; +} diff --git a/users/wpcarro/assessments/tt/client/src/Admin.elm b/users/wpcarro/assessments/tt/client/src/Admin.elm new file mode 100644 index 000000000..d95609ee1 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Admin.elm @@ -0,0 +1,189 @@ +module Admin exposing (render) + +import Common +import Date +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Maybe.Extra as ME +import RemoteData +import State +import Tailwind +import UI +import Utils + + +roleToggle : State.Model -> State.Role -> Html State.Msg +roleToggle model role = + div [ [ "px-1", "inline" ] |> Tailwind.use |> class ] + [ UI.toggleButton + { toggled = model.inviteRole == Just role + , label = State.roleToString role + , handleEnable = State.UpdateInviteRole (Just role) + , handleDisable = State.UpdateInviteRole Nothing + } + ] + + +inviteUser : State.Model -> Html State.Msg +inviteUser model = + div [ [ "pb-6" ] |> Tailwind.use |> class ] + [ UI.header 3 "Invite a user" + , UI.textField + { handleInput = State.UpdateInviteEmail + , inputId = "invite-email" + , inputValue = model.inviteEmail + , pholder = "Email..." + } + , div [ [ "pt-4" ] |> Tailwind.use |> class ] + [ roleToggle model State.User + , roleToggle model State.Manager + , roleToggle model State.Admin + ] + , UI.baseButton + { enabled = + List.all + identity + [ String.length model.inviteEmail > 0 + , ME.isJust model.inviteRole + ] + , extraClasses = [ "my-4" ] + , label = + case model.inviteResponseStatus of + RemoteData.Loading -> + "Sending..." + + _ -> + "Send invitation" + , handleClick = + case model.inviteRole of + Nothing -> + State.DoNothing + + Just role -> + State.AttemptInviteUser role + } + ] + + +allTrips : State.Model -> Html State.Msg +allTrips model = + case model.trips of + RemoteData.NotAsked -> + UI.absentData { handleFetch = State.AttemptGetTrips } + + RemoteData.Loading -> + UI.paragraph "Loading..." + + RemoteData.Failure e -> + UI.paragraph ("Error: " ++ Utils.explainHttpError e) + + RemoteData.Success xs -> + ul [] + (xs + |> List.map + (\trip -> + li [] + [ UI.paragraph (Date.toIsoString trip.startDate ++ " - " ++ Date.toIsoString trip.endDate ++ ", " ++ trip.username ++ " is going " ++ trip.destination) + , UI.textButton + { label = "delete" + , handleClick = State.AttemptDeleteTrip trip + } + ] + ) + ) + + +allUsers : State.Model -> Html State.Msg +allUsers model = + case model.accounts of + RemoteData.NotAsked -> + UI.absentData { handleFetch = State.AttemptGetAccounts } + + RemoteData.Loading -> + UI.paragraph "Loading..." + + RemoteData.Failure e -> + UI.paragraph ("Error: " ++ Utils.explainHttpError e) + + RemoteData.Success xs -> + ul [] + (xs + |> List.map + (\account -> + li [] + [ UI.paragraph + (account.username + ++ " - " + ++ State.roleToString account.role + ) + , UI.textButton + { label = "delete" + , handleClick = State.AttemptDeleteAccount account.username + } + ] + ) + ) + + +users : List String -> Html State.Msg +users xs = + ul [] + (xs + |> List.map + (\x -> + li [ [ "py-4", "flex" ] |> Tailwind.use |> class ] + [ p [ [ "flex-1" ] |> Tailwind.use |> class ] [ text x ] + , div [ [ "flex-1" ] |> Tailwind.use |> class ] + [ UI.simpleButton + { label = "Delete" + , handleClick = State.AttemptDeleteAccount x + } + ] + ] + ) + ) + + +render : State.Model -> Html State.Msg +render model = + div + [ [ "container" + , "mx-auto" + , "text-center" + ] + |> Tailwind.use + |> class + ] + [ UI.header 2 "Welcome!" + , div [] + [ UI.textButton + { label = "Logout" + , handleClick = State.AttemptLogout + } + ] + , div [ [ "py-3" ] |> Tailwind.use |> class ] + [ case model.adminTab of + State.Accounts -> + UI.textButton + { label = "Switch to trips" + , handleClick = State.UpdateAdminTab State.Trips + } + + State.Trips -> + UI.textButton + { label = "Switch to accounts" + , handleClick = State.UpdateAdminTab State.Accounts + } + ] + , case model.adminTab of + State.Accounts -> + div [] + [ inviteUser model + , allUsers model + ] + + State.Trips -> + allTrips model + , Common.allErrors model + ] diff --git a/users/wpcarro/assessments/tt/client/src/Common.elm b/users/wpcarro/assessments/tt/client/src/Common.elm new file mode 100644 index 000000000..63ba97b79 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Common.elm @@ -0,0 +1,37 @@ +module Common exposing (..) + +import Html exposing (..) +import Maybe.Extra as ME +import State +import UI +import Utils + + +allErrors : State.Model -> Html State.Msg +allErrors model = + div [] + (State.allErrors + model + |> List.map + (\( mError, title ) -> + case mError of + Nothing -> + text "" + + Just err -> + UI.errorBanner + { title = title + , body = Utils.explainHttpError err + } + ) + ) + + +withSession : State.Model -> (State.Session -> Html State.Msg) -> Html State.Msg +withSession model renderWithSession = + case model.session of + Nothing -> + div [] [ UI.paragraph "You need a valid session to view this page. Please attempt to log in." ] + + Just session -> + renderWithSession session diff --git a/users/wpcarro/assessments/tt/client/src/Login.elm b/users/wpcarro/assessments/tt/client/src/Login.elm new file mode 100644 index 000000000..b1a436098 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Login.elm @@ -0,0 +1,199 @@ +module Login exposing (render) + +import Common +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import State +import Tailwind +import UI +import Utils + + +googleSignIn : Html State.Msg +googleSignIn = + div + [ class "g-signin2" + , attribute "onsuccess" "onSignIn" + , onClick State.GoogleSignIn + ] + [] + + +loginForm : State.Model -> Html State.Msg +loginForm model = + div + [ [ "w-full" + , "max-w-xs" + , "mx-auto" + ] + |> Tailwind.use + |> class + ] + [ div + [ [ "bg-white" + , "shadow-md" + , "rounded" + , "px-8" + , "pt-6" + , "pb-8" + , "mb-4" + , "text-left" + ] + |> Tailwind.use + |> class + ] + [ div [ [ "text-center", "pb-6" ] |> Tailwind.use |> class ] + [ UI.textButton + { handleClick = State.ToggleLoginForm + , label = + case model.loginTab of + State.LoginForm -> + "Switch to sign up" + + State.SignUpForm -> + "Switch to login" + } + ] + , div + [ [ "mb-4" ] |> Tailwind.use |> class ] + [ UI.label_ { for_ = "username", text_ = "Username" } + , UI.textField + { inputId = "Username" + , pholder = "Username" + , handleInput = State.UpdateUsername + , inputValue = model.username + } + ] + , case model.loginTab of + State.LoginForm -> + text "" + + State.SignUpForm -> + div + [ [ "mb-4" ] |> Tailwind.use |> class ] + [ UI.label_ { for_ = "email", text_ = "Email" } + , input + [ [ "shadow" + , "appearance-none" + , "border" + , "rounded" + , "w-full" + , "py-2" + , "px-3" + , "text-gray-700" + , "leading-tight" + , "focus:outline-none" + , "focus:shadow-outline" + ] + |> Tailwind.use + |> class + , id "email" + , placeholder "who@domain.tld" + , onInput State.UpdateEmail + ] + [] + ] + , div + [ [ "mb-4" ] |> Tailwind.use |> class ] + [ UI.label_ { for_ = "password", text_ = "Password" } + , input + [ [ "shadow" + , "appearance-none" + , "border" + , "rounded" + , "w-full" + , "py-2" + , "px-3" + , "text-gray-700" + , "leading-tight" + , "focus:outline-none" + , "focus:shadow-outline" + ] + |> Tailwind.use + |> class + , id "password" + , type_ "password" + , placeholder "******************" + , onInput State.UpdatePassword + ] + [] + ] + , case model.loginTab of + State.LoginForm -> + div [ [ "flex", "space-around" ] |> Tailwind.use |> class ] + [ UI.simpleButton + { handleClick = State.AttemptLogin + , label = "Login" + } + , div [ [ "pl-4" ] |> Tailwind.use |> class ] [ googleSignIn ] + ] + + State.SignUpForm -> + if + List.all identity + [ String.length model.username > 0 + , String.length model.email > 0 + , String.length model.password > 0 + ] + then + div [] + [ UI.simpleButton + { handleClick = State.AttemptSignUp + , label = "Sign up" + } + ] + + else + UI.disabledButton { label = "Sign up" } + ] + ] + + +login : + State.Model + -> Html State.Msg +login model = + div + [ [ "text-center" + , "py-20" + , "bg-gray-200" + , "h-screen" + ] + |> Tailwind.use + |> class + ] + [ UI.header 3 "Welcome to Trip Planner" + , loginForm model + , Common.allErrors model + ] + + +logout : State.Model -> Html State.Msg +logout model = + div + [ [ "text-center" + , "py-20" + , "bg-gray-200" + , "h-screen" + ] + |> Tailwind.use + |> class + ] + [ UI.header 3 "Looks like you're already signed in..." + , UI.simpleButton + { label = "Logout" + , handleClick = State.AttemptLogout + } + , Common.allErrors model + ] + + +render : State.Model -> Html State.Msg +render model = + case model.session of + Nothing -> + login model + + Just x -> + logout model diff --git a/users/wpcarro/assessments/tt/client/src/Main.elm b/users/wpcarro/assessments/tt/client/src/Main.elm new file mode 100644 index 000000000..de71a72db --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Main.elm @@ -0,0 +1,62 @@ +module Main exposing (main) + +import Admin +import Browser +import Html exposing (..) +import Login +import Manager +import State +import Url +import User + + +viewForRoute : State.Route -> (State.Model -> Html State.Msg) +viewForRoute route = + case route of + State.Login -> + Login.render + + State.UserHome -> + User.render + + State.ManagerHome -> + Manager.render + + State.AdminHome -> + Admin.render + + +view : State.Model -> Browser.Document State.Msg +view model = + { title = "TripPlanner" + , body = + [ case ( model.session, model.route ) of + -- Redirect to /login when someone is not authenticated. + -- TODO(wpcarro): We should ensure that /login shows in the URL + -- bar. + ( Nothing, _ ) -> + Login.render model + + ( Just session, Nothing ) -> + Login.render model + + -- Authenticated + ( Just session, Just route ) -> + if State.isAuthorized session.role route then + viewForRoute route model + + else + text "Access denied. You are not authorized to be here. Evacuate the area immediately" + ] + } + + +main = + Browser.application + { init = State.init + , onUrlChange = State.UrlChanged + , onUrlRequest = State.LinkClicked + , subscriptions = \_ -> Sub.none + , update = State.update + , view = view + } diff --git a/users/wpcarro/assessments/tt/client/src/Manager.elm b/users/wpcarro/assessments/tt/client/src/Manager.elm new file mode 100644 index 000000000..cd15c99a3 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Manager.elm @@ -0,0 +1,70 @@ +module Manager exposing (render) + +import Array +import Common +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import RemoteData +import State +import Tailwind +import UI +import Utils + + +allUsers : State.Model -> Html State.Msg +allUsers model = + case model.accounts of + RemoteData.NotAsked -> + UI.absentData { handleFetch = State.AttemptGetAccounts } + + RemoteData.Loading -> + UI.paragraph "Loading..." + + RemoteData.Failure e -> + UI.paragraph ("Error: " ++ Utils.explainHttpError e) + + RemoteData.Success xs -> + ul [] + (xs + |> List.map + (\account -> + li [] + [ UI.paragraph + (account.username + ++ " - " + ++ State.roleToString account.role + ) + , UI.textButton + { label = "delete" + , handleClick = State.AttemptDeleteAccount account.username + } + ] + ) + ) + + +render : State.Model -> Html State.Msg +render model = + Common.withSession model + (\session -> + div + [ class + ([ "container" + , "mx-auto" + , "text-center" + ] + |> Tailwind.use + ) + ] + [ h1 [] + [ UI.header 2 ("Welcome back, " ++ session.username ++ "!") + , UI.textButton + { label = "Logout" + , handleClick = State.AttemptLogout + } + , allUsers model + , Common.allErrors model + ] + ] + ) diff --git a/users/wpcarro/assessments/tt/client/src/Shared.elm b/users/wpcarro/assessments/tt/client/src/Shared.elm new file mode 100644 index 000000000..addb0a4ff --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Shared.elm @@ -0,0 +1,7 @@ +module Shared exposing (..) + +clientOrigin = + "http://localhost:8000" + +serverOrigin = + "http://localhost:3000" diff --git a/users/wpcarro/assessments/tt/client/src/State.elm b/users/wpcarro/assessments/tt/client/src/State.elm new file mode 100644 index 000000000..b3f78bb16 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/State.elm @@ -0,0 +1,1014 @@ +port module State exposing (..) + +import Array exposing (Array) +import Browser +import Browser.Navigation as Nav +import Date +import DatePicker +import Http +import Json.Decode as JD +import Json.Decode.Extra as JDE +import Json.Encode as JE +import Json.Encode.Extra as JEE +import Process +import RemoteData exposing (WebData) +import Shared +import Task +import Time +import Url +import Url.Builder as UrlBuilder +import Url.Parser exposing ((), Parser, int, map, oneOf, s, string) +import Utils + + + +-------------------------------------------------------------------------------- +-- Types +-------------------------------------------------------------------------------- + + +type Msg + = DoNothing + | UpdateUsername String + | UpdateEmail String + | UpdatePassword String + | UpdateRole String + | UpdateAdminTab AdminTab + | UpdateTripDestination String + | UpdateTripStartDate DatePicker.Msg + | UpdateTripEndDate DatePicker.Msg + | UpdateTripComment String + | UpdateEditTripDestination String + | UpdateEditTripComment String + | ClearErrors + | ToggleLoginForm + | PrintPage + | GoogleSignIn + | GoogleSignOut + | UpdateInviteEmail String + | UpdateInviteRole (Maybe Role) + | ReceiveTodaysDate Date.Date + | EditTrip Trip + | CancelEditTrip + -- SPA + | LinkClicked Browser.UrlRequest + | UrlChanged Url.Url + -- Outbound network + | AttemptGetAccounts + | AttemptGetTrips + | AttemptSignUp + | AttemptLogin + | AttemptLogout + | AttemptDeleteAccount String + | AttemptCreateTrip Date.Date Date.Date + | AttemptDeleteTrip Trip + | AttemptInviteUser Role + | AttemptUpdateTrip TripPK Trip + -- Inbound network + | GotAccounts (WebData (List Account)) + | GotTrips (WebData (List Trip)) + | GotSignUp (Result Http.Error Session) + | GotLogin (Result Http.Error Session) + | GotLogout (Result Http.Error String) + | GotDeleteAccount (Result Http.Error String) + | GotCreateTrip (Result Http.Error ()) + | GotDeleteTrip (Result Http.Error ()) + | GotInviteUser (Result Http.Error ()) + | GotUpdateTrip (Result Http.Error ()) + + +type Route + = Login + | UserHome + | ManagerHome + | AdminHome + + +type Role + = User + | Manager + | Admin + + +type alias Account = + { username : String + , role : Role + } + + +type alias Session = + { role : Role + , username : String + } + + +type alias Review = + { rowid : Int + , content : String + , rating : Int + , user : String + , dateOfVisit : String + } + + +type AdminTab + = Accounts + | Trips + + +type LoginTab + = LoginForm + | SignUpForm + + +type alias Trip = + { username : String + , destination : String + , startDate : Date.Date + , endDate : Date.Date + , comment : String + } + + +type alias TripPK = + { username : String + , destination : String + , startDate : Date.Date + } + + +type alias Model = + { route : Maybe Route + , url : Url.Url + , key : Nav.Key + , session : Maybe Session + , todaysDate : Maybe Date.Date + , username : String + , email : String + , password : String + , role : Maybe Role + , accounts : WebData (List Account) + , startDatePicker : DatePicker.DatePicker + , endDatePicker : DatePicker.DatePicker + , tripDestination : String + , tripStartDate : Maybe Date.Date + , tripEndDate : Maybe Date.Date + , tripComment : String + , trips : WebData (List Trip) + , editingTrip : Maybe Trip + , editTripDestination : String + , editTripComment : String + , adminTab : AdminTab + , loginTab : LoginTab + , inviteEmail : String + , inviteRole : Maybe Role + , inviteResponseStatus : WebData () + , updateTripStatus : WebData () + , loginError : Maybe Http.Error + , logoutError : Maybe Http.Error + , signUpError : Maybe Http.Error + , deleteUserError : Maybe Http.Error + , createTripError : Maybe Http.Error + , deleteTripError : Maybe Http.Error + , inviteUserError : Maybe Http.Error + } + + +allErrors : Model -> List ( Maybe Http.Error, String ) +allErrors model = + [ ( model.loginError, "Error attempting to authenticate" ) + , ( model.logoutError, "Error attempting to log out" ) + , ( model.signUpError, "Error attempting to create your account" ) + , ( model.deleteUserError, "Error attempting to delete a user" ) + , ( model.createTripError, "Error attempting to create a trip" ) + , ( model.inviteUserError, "Error attempting to invite a user" ) + ] + + + +-------------------------------------------------------------------------------- +-- Functions +-------------------------------------------------------------------------------- + + +roleToString : Role -> String +roleToString role = + case role of + User -> + "user" + + Manager -> + "manager" + + Admin -> + "admin" + + +endpoint : List String -> List UrlBuilder.QueryParameter -> String +endpoint = + UrlBuilder.crossOrigin Shared.serverOrigin + + +encodeRole : Role -> JE.Value +encodeRole x = + case x of + User -> + JE.string "user" + + Manager -> + JE.string "manager" + + Admin -> + JE.string "admin" + + +decodeRole : JD.Decoder Role +decodeRole = + let + toRole : String -> JD.Decoder Role + toRole s = + case s of + "user" -> + JD.succeed User + + "manager" -> + JD.succeed Manager + + "admin" -> + JD.succeed Admin + + x -> + JD.fail ("Invalid input: " ++ x) + in + JD.string |> JD.andThen toRole + + +decodeSession : JD.Decoder Session +decodeSession = + JD.map2 + Session + (JD.field "role" decodeRole) + (JD.field "username" JD.string) + + +encodeLoginRequest : String -> String -> JE.Value +encodeLoginRequest username password = + JE.object + [ ( "username", JE.string username ) + , ( "password", JE.string password ) + ] + + +login : String -> String -> Cmd Msg +login username password = + Utils.postWithCredentials + { url = endpoint [ "login" ] [] + , body = Http.jsonBody (encodeLoginRequest username password) + , expect = Http.expectJson GotLogin decodeSession + } + + +logout : Cmd Msg +logout = + Utils.getWithCredentials + { url = endpoint [ "logout" ] [] + , expect = Http.expectString GotLogout + } + + +signUp : + { username : String + , email : String + , password : String + } + -> Cmd Msg +signUp { username, email, password } = + Utils.postWithCredentials + { url = endpoint [ "accounts" ] [] + , body = + Http.jsonBody + (JE.object + [ ( "username", JE.string username ) + , ( "email", JE.string username ) + , ( "password", JE.string password ) + , ( "role", JE.string "user" ) + ] + ) + , expect = Http.expectJson GotSignUp decodeSession + } + + +updateTrip : TripPK -> Trip -> Cmd Msg +updateTrip tripKey trip = + Utils.putWithCredentials + { url = endpoint [ "trips" ] [] + , body = + Http.jsonBody + (JE.object + [ ( "tripKey", encodeTripKey tripKey ) + , ( "destination", JE.string trip.destination ) + , ( "startDate", encodeDate trip.startDate ) + , ( "endDate", encodeDate trip.endDate ) + , ( "comment", JE.string trip.comment ) + ] + ) + , expect = Http.expectWhatever GotUpdateTrip + } + + +inviteUser : { email : String, role : Role } -> Cmd Msg +inviteUser { email, role } = + Utils.postWithCredentials + { url = endpoint [ "invite" ] [] + , body = + Http.jsonBody + (JE.object + [ ( "email", JE.string email ) + , ( "role", encodeRole role ) + ] + ) + , expect = Http.expectWhatever GotInviteUser + } + + +createTrip : + { username : String + , destination : String + , startDate : Date.Date + , endDate : Date.Date + , comment : String + } + -> Cmd Msg +createTrip { username, destination, startDate, endDate, comment } = + Utils.postWithCredentials + { url = endpoint [ "trips" ] [] + , body = + Http.jsonBody + (JE.object + [ ( "username", JE.string username ) + , ( "destination", JE.string destination ) + , ( "startDate", encodeDate startDate ) + , ( "endDate", encodeDate endDate ) + , ( "comment", JE.string comment ) + ] + ) + , expect = Http.expectWhatever GotCreateTrip + } + + +deleteTrip : + { username : String + , destination : String + , startDate : Date.Date + } + -> Cmd Msg +deleteTrip { username, destination, startDate } = + Utils.deleteWithCredentials + { url = endpoint [ "trips" ] [] + , body = + Http.jsonBody + (JE.object + [ ( "username", JE.string username ) + , ( "destination", JE.string destination ) + , ( "startDate", encodeDate startDate ) + ] + ) + , expect = Http.expectWhatever GotDeleteTrip + } + + +deleteAccount : String -> Cmd Msg +deleteAccount username = + Utils.deleteWithCredentials + { url = endpoint [ "accounts" ] [ UrlBuilder.string "username" username ] + , body = Http.emptyBody + , expect = Http.expectString GotDeleteAccount + } + + +decodeReview : JD.Decoder Review +decodeReview = + JD.map5 + Review + (JD.field "rowid" JD.int) + (JD.field "content" JD.string) + (JD.field "rating" JD.int) + (JD.field "user" JD.string) + (JD.field "timestamp" JD.string) + + +encodeTripKey : TripPK -> JE.Value +encodeTripKey tripKey = + JE.object + [ ( "username", JE.string tripKey.username ) + , ( "destination", JE.string tripKey.destination ) + , ( "startDate", encodeDate tripKey.startDate ) + ] + + +encodeDate : Date.Date -> JE.Value +encodeDate date = + date |> Date.toIsoString |> JE.string + + +decodeDate : JD.Decoder Date.Date +decodeDate = + JD.string |> JD.andThen (Date.fromIsoString >> JDE.fromResult) + + +fetchTrips : Cmd Msg +fetchTrips = + Utils.getWithCredentials + { url = endpoint [ "trips" ] [] + , expect = + Http.expectJson + (RemoteData.fromResult >> GotTrips) + (JD.list + (JD.map5 + Trip + (JD.field "username" JD.string) + (JD.field "destination" JD.string) + (JD.field "startDate" decodeDate) + (JD.field "endDate" decodeDate) + (JD.field "comment" JD.string) + ) + ) + } + + +fetchAccounts : Cmd Msg +fetchAccounts = + Utils.getWithCredentials + { url = endpoint [ "accounts" ] [] + , expect = + Http.expectJson + (RemoteData.fromResult >> GotAccounts) + (JD.list + (JD.map2 + Account + (JD.field "username" JD.string) + (JD.field "role" decodeRole) + ) + ) + } + + +sleepAndClearErrors : Cmd Msg +sleepAndClearErrors = + Process.sleep 4000 + |> Task.perform (\_ -> ClearErrors) + + +isAuthorized : Role -> Route -> Bool +isAuthorized role route = + case ( role, route ) of + ( User, _ ) -> + True + + ( Manager, _ ) -> + True + + ( Admin, _ ) -> + True + + +homeRouteForRole : Role -> String +homeRouteForRole role = + case role of + User -> + "/user" + + Manager -> + "/manager" + + Admin -> + "/admin" + + +routeParser : Parser (Route -> a) a +routeParser = + oneOf + [ map Login (s "topic") + , map UserHome (s "user") + , map ManagerHome (s "manager") + , map AdminHome (s "admin") + ] + + +{-| Set init to `prod` when going live. +-} +prod : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +prod _ url key = + let + ( startDatePicker, startDatePickerCmd ) = + DatePicker.init + + ( endDatePicker, endDatePickerCmd ) = + DatePicker.init + in + ( { route = Nothing + , url = url + , key = key + , session = Nothing + , todaysDate = Nothing + , username = "" + , email = "" + , password = "" + , role = Nothing + , accounts = RemoteData.NotAsked + , tripDestination = "" + , tripStartDate = Nothing + , tripEndDate = Nothing + , tripComment = "" + , trips = RemoteData.NotAsked + , editingTrip = Nothing + , editTripDestination = "" + , editTripComment = "" + , startDatePicker = startDatePicker + , endDatePicker = endDatePicker + , adminTab = Accounts + , loginTab = LoginForm + , inviteEmail = "" + , inviteRole = Nothing + , inviteResponseStatus = RemoteData.NotAsked + , updateTripStatus = RemoteData.NotAsked + , loginError = Nothing + , logoutError = Nothing + , signUpError = Nothing + , deleteUserError = Nothing + , createTripError = Nothing + , deleteTripError = Nothing + , inviteUserError = Nothing + } + , Cmd.batch + [ Cmd.map UpdateTripStartDate startDatePickerCmd + , Cmd.map UpdateTripEndDate endDatePickerCmd + , Date.today |> Task.perform ReceiveTodaysDate + ] + ) + + +{-| When working on a feature for the UserHome, use this. +-} +userHome : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +userHome flags url key = + let + ( model, cmd ) = + prod flags url key + in + ( { model + | route = Just UserHome + , session = Just { username = "mimi", role = User } + , trips = + RemoteData.Success + [ { username = "mimi" + , destination = "Barcelona" + , startDate = Date.fromCalendarDate 2020 Time.Sep 25 + , endDate = Date.fromCalendarDate 2020 Time.Oct 5 + , comment = "Blah" + } + , { username = "mimi" + , destination = "Paris" + , startDate = Date.fromCalendarDate 2021 Time.Jan 1 + , endDate = Date.fromCalendarDate 2021 Time.Feb 1 + , comment = "Bon voyage!" + } + ] + } + , cmd + ) + + +managerHome : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +managerHome flags url key = + let + ( model, cmd ) = + prod flags url key + in + ( { model + | route = Just ManagerHome + , session = Just { username = "bill", role = Manager } + } + , cmd + ) + + +adminHome : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +adminHome flags url key = + let + ( model, cmd ) = + prod flags url key + in + ( { model + | route = Just AdminHome + , session = Just { username = "wpcarro", role = Admin } + } + , cmd + ) + + +port printPage : () -> Cmd msg + + +port googleSignIn : () -> Cmd msg + + +port googleSignOut : () -> Cmd msg + + +{-| The initial state for the application. +-} +init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +init flags url key = + prod flags url key + + +{-| Now that we have state, we need a function to change the state. +-} +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + DoNothing -> + ( model, Cmd.none ) + + UpdateUsername x -> + ( { model | username = x }, Cmd.none ) + + UpdatePassword x -> + ( { model | password = x }, Cmd.none ) + + UpdateEmail x -> + ( { model | email = x }, Cmd.none ) + + UpdateAdminTab x -> + ( { model | adminTab = x }, Cmd.none ) + + UpdateRole x -> + let + maybeRole = + case x of + "user" -> + Just User + + "manager" -> + Just Manager + + "admin" -> + Just Admin + + _ -> + Nothing + in + ( { model | role = maybeRole }, Cmd.none ) + + UpdateTripDestination x -> + ( { model | tripDestination = x }, Cmd.none ) + + UpdateTripStartDate dpMsg -> + let + ( newDatePicker, dateEvent ) = + DatePicker.update DatePicker.defaultSettings dpMsg model.startDatePicker + + newDate = + case dateEvent of + DatePicker.Picked changedDate -> + Just changedDate + + _ -> + model.tripStartDate + in + ( { model + | tripStartDate = newDate + , startDatePicker = newDatePicker + } + , Cmd.none + ) + + UpdateTripEndDate dpMsg -> + let + ( newDatePicker, dateEvent ) = + DatePicker.update DatePicker.defaultSettings dpMsg model.endDatePicker + + newDate = + case dateEvent of + DatePicker.Picked changedDate -> + Just changedDate + + _ -> + model.tripEndDate + in + ( { model + | tripEndDate = newDate + , endDatePicker = newDatePicker + } + , Cmd.none + ) + + UpdateTripComment x -> + ( { model | tripComment = x }, Cmd.none ) + + UpdateEditTripDestination x -> + ( { model | editTripDestination = x }, Cmd.none ) + + UpdateEditTripComment x -> + ( { model | editTripComment = x }, Cmd.none ) + + ClearErrors -> + ( { model + | loginError = Nothing + , logoutError = Nothing + , signUpError = Nothing + , deleteUserError = Nothing + , createTripError = Nothing + } + , Cmd.none + ) + + ToggleLoginForm -> + ( { model + | loginTab = + case model.loginTab of + LoginForm -> + SignUpForm + + SignUpForm -> + LoginForm + } + , Cmd.none + ) + + PrintPage -> + ( model, printPage () ) + + GoogleSignIn -> + ( model, googleSignIn () ) + + GoogleSignOut -> + ( model, googleSignOut () ) + + UpdateInviteEmail x -> + ( { model | inviteEmail = x }, Cmd.none ) + + UpdateInviteRole mRole -> + ( { model | inviteRole = mRole }, Cmd.none ) + + ReceiveTodaysDate date -> + ( { model | todaysDate = Just date }, Cmd.none ) + + EditTrip trip -> + ( { model + | editingTrip = Just trip + , editTripDestination = trip.destination + , editTripComment = trip.comment + } + , Cmd.none + ) + + CancelEditTrip -> + ( { model + | editingTrip = Nothing + , editTripDestination = "" + , editTripComment = "" + } + , Cmd.none + ) + + LinkClicked urlRequest -> + case urlRequest of + Browser.Internal url -> + ( model, Nav.pushUrl model.key (Url.toString url) ) + + Browser.External href -> + ( model, Nav.load href ) + + UrlChanged url -> + let + route = + Url.Parser.parse routeParser url + in + case route of + Just UserHome -> + ( { model + | url = url + , route = route + , trips = RemoteData.Loading + } + , fetchTrips + ) + + Just ManagerHome -> + ( { model + | url = url + , route = route + , accounts = RemoteData.Loading + } + , fetchAccounts + ) + + Just AdminHome -> + ( { model + | url = url + , route = route + , accounts = RemoteData.Loading + , trips = RemoteData.Loading + } + , Cmd.batch + [ fetchAccounts + , fetchTrips + ] + ) + + _ -> + ( { model + | url = url + , route = route + } + , Cmd.none + ) + + -- GET /accounts + AttemptGetAccounts -> + ( { model | accounts = RemoteData.Loading }, fetchAccounts ) + + GotAccounts xs -> + ( { model | accounts = xs }, Cmd.none ) + + -- DELETE /accounts + AttemptDeleteAccount username -> + ( model, deleteAccount username ) + + GotDeleteAccount result -> + case result of + Ok _ -> + ( model, fetchAccounts ) + + Err e -> + ( { model | deleteUserError = Just e } + , sleepAndClearErrors + ) + + -- POST /trips + AttemptCreateTrip startDate endDate -> + ( model + , case model.session of + Nothing -> + Cmd.none + + Just session -> + createTrip + { username = session.username + , destination = model.tripDestination + , startDate = startDate + , endDate = endDate + , comment = model.tripComment + } + ) + + GotCreateTrip result -> + case result of + Ok _ -> + ( { model + | tripDestination = "" + , tripStartDate = Nothing + , tripEndDate = Nothing + , tripComment = "" + } + , fetchTrips + ) + + Err e -> + ( { model + | createTripError = Just e + , tripDestination = "" + , tripStartDate = Nothing + , tripEndDate = Nothing + , tripComment = "" + } + , sleepAndClearErrors + ) + + -- DELETE /trips + AttemptDeleteTrip trip -> + ( model + , deleteTrip + { username = trip.username + , destination = trip.destination + , startDate = trip.startDate + } + ) + + GotDeleteTrip result -> + case result of + Ok _ -> + ( model, fetchTrips ) + + Err e -> + ( { model | deleteTripError = Just e } + , sleepAndClearErrors + ) + + AttemptInviteUser role -> + ( { model | inviteResponseStatus = RemoteData.Loading } + , inviteUser + { email = model.inviteEmail + , role = role + } + ) + + GotInviteUser result -> + case result of + Ok _ -> + ( { model + | inviteEmail = "" + , inviteRole = Nothing + , inviteResponseStatus = RemoteData.Success () + } + , Cmd.none + ) + + Err e -> + ( { model + | inviteUserError = Just e + , inviteResponseStatus = RemoteData.Failure e + } + , sleepAndClearErrors + ) + + -- PATCH /trips + AttemptUpdateTrip tripKey trip -> + ( { model | updateTripStatus = RemoteData.Loading } + , updateTrip tripKey trip + ) + + GotUpdateTrip result -> + case result of + Ok _ -> + ( { model | updateTripStatus = RemoteData.Success () } + , fetchTrips + ) + + Err e -> + ( { model | updateTripStatus = RemoteData.Failure e } + , Cmd.none + ) + + -- POST /accounts + AttemptSignUp -> + ( model + , signUp + { username = model.username + , email = model.email + , password = model.password + } + ) + + GotSignUp result -> + case result of + Ok session -> + ( { model | session = Just session } + , Nav.pushUrl model.key (homeRouteForRole session.role) + ) + + Err x -> + ( { model | signUpError = Just x } + , sleepAndClearErrors + ) + + -- GET /trips + AttemptGetTrips -> + ( { model | trips = RemoteData.Loading }, fetchTrips ) + + GotTrips xs -> + ( { model | trips = xs }, Cmd.none ) + + -- POST /login + AttemptLogin -> + ( model, login model.username model.password ) + + GotLogin result -> + case result of + Ok session -> + ( { model | session = Just session } + , Nav.pushUrl model.key (homeRouteForRole session.role) + ) + + Err x -> + ( { model | loginError = Just x } + , sleepAndClearErrors + ) + + -- GET /logout + AttemptLogout -> + ( model, logout ) + + GotLogout result -> + case result of + Ok _ -> + ( { model | session = Nothing } + , Nav.pushUrl model.key "/login" + ) + + Err e -> + ( { model | logoutError = Just e } + , sleepAndClearErrors + ) diff --git a/users/wpcarro/assessments/tt/client/src/Tailwind.elm b/users/wpcarro/assessments/tt/client/src/Tailwind.elm new file mode 100644 index 000000000..57d419db5 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Tailwind.elm @@ -0,0 +1,29 @@ +module Tailwind exposing (..) + +{-| Functions to make Tailwind development in Elm even more pleasant. +-} + + +{-| Conditionally use `class` selection when `condition` is true. +-} +when : Bool -> String -> String +when condition class = + if condition then + class + + else + "" + + +if_ : Bool -> String -> String -> String +if_ condition whenTrue whenFalse = + if condition then + whenTrue + + else + whenFalse + + +use : List String -> String +use styles = + String.join " " styles diff --git a/users/wpcarro/assessments/tt/client/src/UI.elm b/users/wpcarro/assessments/tt/client/src/UI.elm new file mode 100644 index 000000000..7f8f37979 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/UI.elm @@ -0,0 +1,318 @@ +module UI exposing (..) + +import Date +import DatePicker exposing (defaultSettings) +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import State +import Tailwind + + +label_ : { for_ : String, text_ : String } -> Html msg +label_ { for_, text_ } = + label + [ [ "block" + , "text-gray-700" + , "text-sm" + , "font-bold" + , "mb-2" + ] + |> Tailwind.use + |> class + , for for_ + ] + [ text text_ ] + + +errorBanner : { title : String, body : String } -> Html msg +errorBanner { title, body } = + div + [ [ "text-left" + , "fixed" + , "container" + , "top-0" + , "mt-6" + ] + |> Tailwind.use + |> class + , style "left" "50%" + + -- TODO(wpcarro): Consider supporting breakpoints, but for now + -- don't. + , style "width" "800px" + , style "margin-left" "-400px" + ] + [ div + [ [ "bg-red-500" + , "text-white" + , "font-bold" + , "rounded-t" + , "px-4" + , "py-2" + ] + |> Tailwind.use + |> class + ] + [ text title ] + , div + [ [ "border" + , "border-t-0" + , "border-red-400" + , "rounded-b" + , "bg-red-100" + , "px-4" + , "py-3" + , "text-red-700" + ] + |> Tailwind.use + |> class + ] + [ p [] [ text body ] ] + ] + + +baseButton : + { label : String + , enabled : Bool + , handleClick : msg + , extraClasses : List String + } + -> Html msg +baseButton { label, enabled, handleClick, extraClasses } = + button + [ [ if enabled then + "bg-blue-500" + + else + "bg-gray-500" + , if enabled then + "hover:bg-blue-700" + + else + "" + , if enabled then + "" + + else + "cursor-not-allowed" + , "text-white" + , "font-bold" + , "py-1" + , "shadow-lg" + , "px-4" + , "rounded" + , "focus:outline-none" + , "focus:shadow-outline" + ] + ++ extraClasses + |> Tailwind.use + |> class + , onClick handleClick + , disabled (not enabled) + ] + [ text label ] + + +simpleButton : + { label : String + , handleClick : msg + } + -> Html msg +simpleButton { label, handleClick } = + baseButton + { label = label + , enabled = True + , handleClick = handleClick + , extraClasses = [] + } + + +disabledButton : + { label : String } + -> Html State.Msg +disabledButton { label } = + baseButton + { label = label + , enabled = False + , handleClick = State.DoNothing + , extraClasses = [] + } + + +textButton : + { label : String + , handleClick : msg + } + -> Html msg +textButton { label, handleClick } = + button + [ [ "text-blue-600" + , "hover:text-blue-500" + , "font-bold" + , "hover:underline" + , "focus:outline-none" + ] + |> Tailwind.use + |> class + , onClick handleClick + ] + [ text label ] + + +textField : + { pholder : String + , inputId : String + , handleInput : String -> msg + , inputValue : String + } + -> Html msg +textField { pholder, inputId, handleInput, inputValue } = + input + [ [ "shadow" + , "appearance-none" + , "border" + , "rounded" + , "w-full" + , "py-2" + , "px-3" + , "text-gray-700" + , "leading-tight" + , "focus:outline-none" + , "focus:shadow-outline" + ] + |> Tailwind.use + |> class + , id inputId + , value inputValue + , placeholder pholder + , onInput handleInput + ] + [] + + +toggleButton : + { toggled : Bool + , label : String + , handleEnable : msg + , handleDisable : msg + } + -> Html msg +toggleButton { toggled, label, handleEnable, handleDisable } = + button + [ [ if toggled then + "bg-blue-700" + + else + "bg-blue-500" + , "hover:bg-blue-700" + , "text-white" + , "font-bold" + , "py-2" + , "px-4" + , "rounded" + , "focus:outline-none" + , "focus:shadow-outline" + ] + |> Tailwind.use + |> class + , onClick + (if toggled then + handleDisable + + else + handleEnable + ) + ] + [ text label ] + + +paragraph : String -> Html msg +paragraph x = + p [ [ "text-xl" ] |> Tailwind.use |> class ] [ text x ] + + +header : Int -> String -> Html msg +header which x = + let + hStyles = + case which of + 1 -> + [ "text-6xl" + , "py-12" + ] + + 2 -> + [ "text-3xl" + , "py-6" + ] + + _ -> + [ "text-2xl" + , "py-2" + ] + in + h1 + [ hStyles + ++ [ "font-bold" + , "text-gray-700" + ] + |> Tailwind.use + |> class + ] + [ text x ] + + +link : String -> String -> Html msg +link path label = + a + [ href path + , [ "underline" + , "text-blue-600" + , "text-xl" + ] + |> Tailwind.use + |> class + ] + [ text label ] + + +absentData : { handleFetch : msg } -> Html msg +absentData { handleFetch } = + div [] + [ paragraph "Welp... it looks like you've caught us in a state that we considered impossible: we did not fetch the data upon which this page depends. Maybe you can help us out by clicking the super secret, highly privileged \"Fetch data\" button below (we don't normally show people this)." + , div [ [ "py-4" ] |> Tailwind.use |> class ] + [ simpleButton + { label = "Fetch data" + , handleClick = handleFetch + } + ] + ] + + +datePicker : + { mDate : Maybe Date.Date + , prompt : String + , prefix : String + , picker : DatePicker.DatePicker + , onUpdate : DatePicker.Msg -> State.Msg + } + -> Html State.Msg +datePicker { mDate, prompt, prefix, picker, onUpdate } = + let + settings = + { defaultSettings + | placeholder = prompt + , inputClassList = + [ ( "text-center", True ) + , ( "py-2", True ) + ] + } + in + div [ [ "w-1/2", "py-4", "mx-auto" ] |> Tailwind.use |> class ] + [ DatePicker.view mDate settings picker |> Html.map onUpdate ] + + +wrapNoPrint : Html State.Msg -> Html State.Msg +wrapNoPrint component = + div [ [ "no-print" ] |> Tailwind.use |> class ] [ component ] diff --git a/users/wpcarro/assessments/tt/client/src/User.elm b/users/wpcarro/assessments/tt/client/src/User.elm new file mode 100644 index 000000000..87871b78d --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/User.elm @@ -0,0 +1,245 @@ +module User exposing (render) + +import Common +import Date +import DatePicker +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Maybe.Extra as ME +import RemoteData +import State +import Tailwind +import UI +import Utils + + +createTrip : State.Model -> Html State.Msg +createTrip model = + div [] + [ UI.header 3 "Plan Upcoming Trip" + , UI.textField + { pholder = "Where are you going?" + , inputId = "destination" + , handleInput = State.UpdateTripDestination + , inputValue = model.tripDestination + } + , div [ [ "flex" ] |> Tailwind.use |> class ] + [ UI.datePicker + { mDate = model.tripStartDate + , prompt = "Set departure date" + , prefix = "Departure: " + , picker = model.startDatePicker + , onUpdate = State.UpdateTripStartDate + } + , UI.datePicker + { mDate = model.tripEndDate + , prompt = "Set return date" + , prefix = "Return: " + , picker = model.endDatePicker + , onUpdate = State.UpdateTripEndDate + } + ] + , UI.textField + { pholder = "Comments?" + , inputId = "comment" + , handleInput = State.UpdateTripComment + , inputValue = model.tripComment + } + , UI.baseButton + { enabled = + List.all + identity + [ String.length model.tripDestination > 0 + , String.length model.tripComment > 0 + , ME.isJust model.tripStartDate + , ME.isJust model.tripEndDate + ] + , extraClasses = [ "my-4" ] + , handleClick = + case ( model.tripStartDate, model.tripEndDate ) of + ( Nothing, _ ) -> + State.DoNothing + + ( _, Nothing ) -> + State.DoNothing + + ( Just startDate, Just endDate ) -> + State.AttemptCreateTrip startDate endDate + , label = "Schedule trip" + } + ] + + +renderEditTrip : State.Model -> State.Trip -> Html State.Msg +renderEditTrip model trip = + li [] + [ div [] + [ UI.textField + { handleInput = State.UpdateEditTripDestination + , inputId = "edit-trip-destination" + , inputValue = model.editTripDestination + , pholder = "Destination" + } + , UI.textField + { handleInput = State.UpdateEditTripComment + , inputId = "edit-trip-comment" + , inputValue = model.editTripComment + , pholder = "Comment" + } + ] + , div [] + [ UI.baseButton + { enabled = + case model.updateTripStatus of + RemoteData.Loading -> + False + + _ -> + True + , extraClasses = [] + , label = + case model.updateTripStatus of + RemoteData.Loading -> + "Saving..." + + _ -> + "Save" + , handleClick = + State.AttemptUpdateTrip + { username = trip.username + , destination = trip.destination + , startDate = trip.startDate + } + { username = trip.username + , destination = model.editTripDestination + , startDate = trip.startDate + , endDate = trip.endDate + , comment = model.editTripComment + } + } + , UI.simpleButton + { label = "Cancel" + , handleClick = State.CancelEditTrip + } + ] + ] + + +renderTrip : Date.Date -> State.Trip -> Html State.Msg +renderTrip today trip = + li + [ [ "py-2" ] + |> Tailwind.use + |> class + ] + [ if Date.compare today trip.startDate == GT then + UI.paragraph + (String.fromInt (Date.diff Date.Days trip.startDate today) + ++ " days until you're travelling to " + ++ trip.destination + ++ " for " + ++ String.fromInt + (Date.diff + Date.Days + trip.startDate + trip.endDate + ) + ++ " days." + ) + + else + UI.paragraph + (String.fromInt (Date.diff Date.Days today trip.endDate) + ++ " days ago you returned from your trip to " + ++ trip.destination + ) + , UI.paragraph ("\"" ++ trip.comment ++ "\"") + , UI.wrapNoPrint + (UI.textButton + { label = "Edit" + , handleClick = State.EditTrip trip + } + ) + , UI.wrapNoPrint + (UI.textButton + { label = "Delete" + , handleClick = State.AttemptDeleteTrip trip + } + ) + ] + + +trips : State.Model -> Html State.Msg +trips model = + div [] + [ UI.header 3 "Your Trips" + , case model.trips of + RemoteData.NotAsked -> + UI.paragraph "Somehow we've reached the user home page without requesting your trips data. Please report this to our engineering team at bugs@tripplaner.tld" + + RemoteData.Loading -> + UI.paragraph "Loading your trips..." + + RemoteData.Failure e -> + UI.paragraph ("Error: " ++ Utils.explainHttpError e) + + RemoteData.Success xs -> + case model.todaysDate of + Nothing -> + text "" + + Just today -> + div [ [ "mb-10" ] |> Tailwind.use |> class ] + [ ul [ [ "my-4" ] |> Tailwind.use |> class ] + (xs + |> List.sortWith (\x y -> Date.compare y.startDate x.startDate) + |> List.map + (\trip -> + case model.editingTrip of + Nothing -> + renderTrip today trip + + Just x -> + if x == trip then + renderEditTrip model trip + + else + renderTrip today trip + ) + ) + , UI.wrapNoPrint + (UI.simpleButton + { label = "Print iternary" + , handleClick = State.PrintPage + } + ) + ] + ] + + +render : State.Model -> Html State.Msg +render model = + Common.withSession model + (\session -> + div + [ class + ([ "container" + , "mx-auto" + , "text-center" + ] + |> Tailwind.use + ) + ] + [ UI.wrapNoPrint (UI.header 2 ("Welcome, " ++ session.username ++ "!")) + , UI.wrapNoPrint (createTrip model) + , trips model + , UI.wrapNoPrint + (UI.textButton + { label = "Logout" + , handleClick = State.AttemptLogout + } + ) + , Common.allErrors model + ] + ) diff --git a/users/wpcarro/assessments/tt/client/src/Utils.elm b/users/wpcarro/assessments/tt/client/src/Utils.elm new file mode 100644 index 000000000..60343cd87 --- /dev/null +++ b/users/wpcarro/assessments/tt/client/src/Utils.elm @@ -0,0 +1,109 @@ +module Utils exposing (..) + +import DateFormat +import Http +import Time +import Shared + + +explainHttpError : Http.Error -> String +explainHttpError e = + case e of + Http.BadUrl _ -> + "Bad URL: you may have supplied an improperly formatted URL" + + Http.Timeout -> + "Timeout: the resource you requested did not arrive within the interval of time that you claimed it should" + + Http.BadStatus s -> + "Bad Status: the server returned a bad status code: " ++ String.fromInt s + + Http.BadBody b -> + "Bad Body: our application had trouble decoding the body of the response from the server: " ++ b + + Http.NetworkError -> + "Network Error: something went awry in the network stack. I recommend checking the server logs if you can." + + +getWithCredentials : + { url : String + , expect : Http.Expect msg + } + -> Cmd msg +getWithCredentials { url, expect } = + Http.riskyRequest + { url = url + , headers = [ Http.header "Origin" Shared.clientOrigin ] + , method = "GET" + , timeout = Nothing + , tracker = Nothing + , body = Http.emptyBody + , expect = expect + } + + +postWithCredentials : + { url : String + , body : Http.Body + , expect : Http.Expect msg + } + -> Cmd msg +postWithCredentials { url, body, expect } = + Http.riskyRequest + { url = url + , headers = [ Http.header "Origin" Shared.clientOrigin ] + , method = "POST" + , timeout = Nothing + , tracker = Nothing + , body = body + , expect = expect + } + + +deleteWithCredentials : + { url : String + , body : Http.Body + , expect : Http.Expect msg + } + -> Cmd msg +deleteWithCredentials { url, body, expect } = + Http.riskyRequest + { url = url + , headers = [ Http.header "Origin" Shared.clientOrigin ] + , method = "DELETE" + , timeout = Nothing + , tracker = Nothing + , body = body + , expect = expect + } + +putWithCredentials : + { url : String + , body : Http.Body + , expect : Http.Expect msg + } + -> Cmd msg +putWithCredentials { url, body, expect } = + Http.riskyRequest + { url = url + , headers = [ Http.header "Origin" Shared.clientOrigin ] + , method = "PUT" + , timeout = Nothing + , tracker = Nothing + , body = body + , expect = expect + } + + + +formatTime : Time.Posix -> String +formatTime ts = + DateFormat.format + [ DateFormat.monthNameFull + , DateFormat.text " " + , DateFormat.dayOfMonthSuffix + , DateFormat.text ", " + , DateFormat.yearNumber + ] + Time.utc + ts diff --git a/users/wpcarro/assessments/tt/data/accounts.csv b/users/wpcarro/assessments/tt/data/accounts.csv new file mode 100644 index 000000000..f5fc77b6d --- /dev/null +++ b/users/wpcarro/assessments/tt/data/accounts.csv @@ -0,0 +1,2 @@ +mimi,$2b$12$LynoGCNbe2RA1WWSiBEMVudJKs5dxnssY16rYmUyiwlSBIhHBOLbu,miriamwright@google.com,user, +wpcarro,$2b$12$3wbi4xfQmksLsu6GOKTbj.5WHywESATnXB4R8FJ55RSRLy6X9xA7u,wpcarro@google.com,admin, \ No newline at end of file diff --git a/users/wpcarro/assessments/tt/data/trips.csv b/users/wpcarro/assessments/tt/data/trips.csv new file mode 100644 index 000000000..a583c750f --- /dev/null +++ b/users/wpcarro/assessments/tt/data/trips.csv @@ -0,0 +1,3 @@ +mimi,Rome,2020-08-10,2020-08-12,Heading home before the upcoming trip with Panarea. +mimi,Panarea,2020-08-15,2020-08-28,Exciting upcoming trip with Matt and Sarah! +mimi,London,2020-08-30,2020-09-15,Heading back to London... \ No newline at end of file diff --git a/users/wpcarro/assessments/tt/populate.sqlite3 b/users/wpcarro/assessments/tt/populate.sqlite3 new file mode 100644 index 000000000..e200d2b49 --- /dev/null +++ b/users/wpcarro/assessments/tt/populate.sqlite3 @@ -0,0 +1,7 @@ +PRAGMA foreign_keys = on; +.read src/init.sql +.mode csv +.import data/accounts.csv Accounts +.import data/trips.csv Trips +.mode column +.headers on \ No newline at end of file diff --git a/users/wpcarro/assessments/tt/shell.nix b/users/wpcarro/assessments/tt/shell.nix new file mode 100644 index 000000000..567b71060 --- /dev/null +++ b/users/wpcarro/assessments/tt/shell.nix @@ -0,0 +1,23 @@ +let + pkgs = import {}; + hailgun-src = builtins.fetchGit { + url = "https://bitbucket.org/echo_rm/hailgun.git"; + rev = "9d5da7c902b2399e0fcf3d494ee04cf2bbfe7c9e"; + }; + hailgun = pkgs.haskellPackages.callCabal2nix "hailgun" hailgun-src {}; +in pkgs.mkShell { + buildInputs = with pkgs; [ + (haskellPackages.ghcWithPackages (hpkgs: with hpkgs; [ + hpkgs.servant-server + hpkgs.aeson + hpkgs.resource-pool + hpkgs.sqlite-simple + hpkgs.wai-cors + hpkgs.warp + hpkgs.cryptonite + hpkgs.uuid + hpkgs.envy + hailgun + ])) + ]; +} diff --git a/users/wpcarro/assessments/tt/src/.ghci b/users/wpcarro/assessments/tt/src/.ghci new file mode 100644 index 000000000..efc88e630 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/.ghci @@ -0,0 +1,2 @@ +:set prompt "> " +:set -Wall diff --git a/users/wpcarro/assessments/tt/src/API.hs b/users/wpcarro/assessments/tt/src/API.hs new file mode 100644 index 000000000..471fa761e --- /dev/null +++ b/users/wpcarro/assessments/tt/src/API.hs @@ -0,0 +1,75 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeOperators #-} +-------------------------------------------------------------------------------- +module API where +-------------------------------------------------------------------------------- +import Data.Text +import Servant.API +import Web.Cookie + +import qualified Types as T +-------------------------------------------------------------------------------- + +-- | Once authenticated, users receive a SessionCookie. +type SessionCookie = Header' '[Required] "Cookie" T.SessionCookie + +type API = + -- accounts: Create + "accounts" + :> Header "Cookie" T.SessionCookie + :> ReqBody '[JSON] T.CreateAccountRequest + :> Post '[JSON] NoContent + :<|> "verify" + :> ReqBody '[JSON] T.VerifyAccountRequest + :> Post '[JSON] NoContent + -- accounts: Read + -- accounts: Update + -- accounts: Delete + :<|> "accounts" + :> SessionCookie + :> QueryParam' '[Required] "username" Text + :> Delete '[JSON] NoContent + -- accounts: List + :<|> "accounts" + :> SessionCookie + :> Get '[JSON] [T.User] + + -- trips: Create + :<|> "trips" + :> SessionCookie + :> ReqBody '[JSON] T.Trip + :> Post '[JSON] NoContent + -- trips: Read + -- trips: Update + :<|> "trips" + :> SessionCookie + :> ReqBody '[JSON] T.UpdateTripRequest + :> Put '[JSON] NoContent + -- trips: Delete + :<|> "trips" + :> SessionCookie + :> ReqBody '[JSON] T.TripPK + :> Delete '[JSON] NoContent + -- trips: List + :<|> "trips" + :> SessionCookie + :> Get '[JSON] [T.Trip] + + -- Miscellaneous + :<|> "login" + :> ReqBody '[JSON] T.AccountCredentials + :> Post '[JSON] (Headers '[Header "Set-Cookie" SetCookie] T.Session) + :<|> "logout" + :> SessionCookie + :> Get '[JSON] (Headers '[Header "Set-Cookie" SetCookie] NoContent) + :<|> "unfreeze" + :> SessionCookie + :> ReqBody '[JSON] T.UnfreezeAccountRequest + :> Post '[JSON] NoContent + :<|> "invite" + :> SessionCookie + :> ReqBody '[JSON] T.InviteUserRequest + :> Post '[JSON] NoContent + :<|> "accept-invitation" + :> ReqBody '[JSON] T.AcceptInvitationRequest + :> Post '[JSON] NoContent diff --git a/users/wpcarro/assessments/tt/src/Accounts.hs b/users/wpcarro/assessments/tt/src/Accounts.hs new file mode 100644 index 000000000..c7ab7a2f1 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Accounts.hs @@ -0,0 +1,49 @@ +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE OverloadedStrings #-} +-------------------------------------------------------------------------------- +module Accounts where +-------------------------------------------------------------------------------- +import Database.SQLite.Simple + +import qualified PendingAccounts +import qualified Types as T +-------------------------------------------------------------------------------- + +-- | Delete the account in PendingAccounts and create on in Accounts. +transferFromPending :: FilePath -> T.PendingAccount -> IO () +transferFromPending dbFile T.PendingAccount{..} = withConnection dbFile $ + \conn -> withTransaction conn $ do + PendingAccounts.delete dbFile pendingAccountUsername + execute conn "INSERT INTO Accounts (username,password,email,role) VALUES (?,?,?,?)" + ( pendingAccountUsername + , pendingAccountPassword + , pendingAccountEmail + , pendingAccountRole + ) + +-- | Create a new account in the Accounts table. +create :: FilePath -> T.Username -> T.ClearTextPassword -> T.Email -> T.Role -> IO () +create dbFile username password email role = withConnection dbFile $ \conn -> do + hashed <- T.hashPassword password + execute conn "INSERT INTO Accounts (username,password,email,role) VALUES (?,?,?,?)" + (username, hashed, email, role) + +-- | Delete `username` from `dbFile`. +delete :: FilePath -> T.Username -> IO () +delete dbFile username = withConnection dbFile $ \conn -> do + execute conn "DELETE FROM Accounts WHERE username = ?" + (Only username) + +-- | Attempt to find `username` in the Account table of `dbFile`. +lookup :: FilePath -> T.Username -> IO (Maybe T.Account) +lookup dbFile username = withConnection dbFile $ \conn -> do + res <- query conn "SELECT username,password,email,role,profilePicture FROM Accounts WHERE username = ?" (Only username) + case res of + [x] -> pure (Just x) + _ -> pure Nothing + +-- | Return a list of accounts with the sensitive data removed. +list :: FilePath -> IO [T.User] +list dbFile = withConnection dbFile $ \conn -> do + accounts <- query_ conn "SELECT username,password,email,role,profilePicture FROM Accounts" + pure $ T.userFromAccount <$> accounts diff --git a/users/wpcarro/assessments/tt/src/App.hs b/users/wpcarro/assessments/tt/src/App.hs new file mode 100644 index 000000000..742bc962d --- /dev/null +++ b/users/wpcarro/assessments/tt/src/App.hs @@ -0,0 +1,270 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TypeApplications #-} +-------------------------------------------------------------------------------- +module App where +-------------------------------------------------------------------------------- +import Control.Monad.IO.Class (liftIO) +import Data.String.Conversions (cs) +import Data.Text (Text) +import Servant +import API +import Utils +import Web.Cookie + +import qualified Network.Wai.Handler.Warp as Warp +import qualified Network.Wai.Middleware.Cors as Cors +import qualified System.Random as Random +import qualified Email as Email +import qualified Data.UUID as UUID +import qualified Types as T +import qualified Accounts as Accounts +import qualified Auth as Auth +import qualified Trips as Trips +import qualified Sessions as Sessions +import qualified Invitations as Invitations +import qualified LoginAttempts as LoginAttempts +import qualified PendingAccounts as PendingAccounts +-------------------------------------------------------------------------------- + +err429 :: ServerError +err429 = ServerError + { errHTTPCode = 429 + , errReasonPhrase = "Too many requests" + , errBody = "" + , errHeaders = [] + } + +-- | Send an email to recipient, `to`, with a secret code. +sendVerifyEmail :: T.Config + -> T.Username + -> T.Email + -> T.RegistrationSecret + -> IO (Either Email.SendError Email.SendSuccess) +sendVerifyEmail T.Config{..} (T.Username username) email (T.RegistrationSecret secretUUID) = do + Email.send mailgunAPIKey subject (cs body) email + where + subject = "Please confirm your account" + body = + let secret = secretUUID |> UUID.toString in + "To verify your account: POST /verify username=" ++ cs username ++ " secret=" ++ secret + +-- | Send an invitation email to recipient, `to`, with a secret code. +sendInviteEmail :: T.Config + -> T.Email + -> T.InvitationSecret + -> IO (Either Email.SendError Email.SendSuccess) +sendInviteEmail T.Config{..} email@(T.Email to) (T.InvitationSecret secretUUID) = do + Email.send mailgunAPIKey subject (cs body) email + where + subject = "You've been invited!" + body = + let secret = secretUUID |> UUID.toString in + "To accept the invitation: POST /accept-invitation username= password= email=" ++ cs to ++ " secret=" ++ secret + +server :: T.Config -> Server API +server config@T.Config{..} = createAccount + :<|> verifyAccount + :<|> deleteAccount + :<|> listAccounts + :<|> createTrip + :<|> updateTrip + :<|> deleteTrip + :<|> listTrips + :<|> login + :<|> logout + :<|> unfreezeAccount + :<|> inviteUser + :<|> acceptInvitation + where + -- Admit Admins + whatever the predicate `p` passes. + adminsAnd cookie p = Auth.assert dbFile cookie (\acct@T.Account{..} -> accountRole == T.Admin || p acct) + -- Admit Admins only. + adminsOnly cookie = adminsAnd cookie (const True) + + -- TODO(wpcarro): Handle failed CONSTRAINTs instead of sending 500s + createAccount :: Maybe T.SessionCookie + -> T.CreateAccountRequest + -> Handler NoContent + createAccount mCookie T.CreateAccountRequest{..} = + case (mCookie, createAccountRequestRole) of + (_, T.RegularUser) -> + doCreateAccount + (Nothing, T.Manager) -> + throwError err401 { errBody = "Only admins can create Manager accounts" } + (Nothing, T.Admin) -> + throwError err401 { errBody = "Only admins can create Admin accounts" } + (Just cookie, _) -> + adminsAnd cookie (\T.Account{..} -> accountRole == T.Manager) doCreateAccount + where + doCreateAccount :: Handler NoContent + doCreateAccount = do + secretUUID <- liftIO $ T.RegistrationSecret <$> Random.randomIO + liftIO $ PendingAccounts.create dbFile + secretUUID + createAccountRequestUsername + createAccountRequestPassword + createAccountRequestRole + createAccountRequestEmail + res <- liftIO $ sendVerifyEmail config + createAccountRequestUsername + createAccountRequestEmail + secretUUID + case res of + Left _ -> undefined + Right _ -> pure NoContent + + verifyAccount :: T.VerifyAccountRequest -> Handler NoContent + verifyAccount T.VerifyAccountRequest{..} = do + mPendingAccount <- liftIO $ PendingAccounts.get dbFile verifyAccountRequestUsername + case mPendingAccount of + Nothing -> + throwError err401 { errBody = "Either your secret or your username (or both) is invalid" } + Just pendingAccount@T.PendingAccount{..} -> + if pendingAccountSecret == verifyAccountRequestSecret then do + liftIO $ Accounts.transferFromPending dbFile pendingAccount + pure NoContent + else + throwError err401 { errBody = "The secret you provided is invalid" } + + deleteAccount :: T.SessionCookie -> Text -> Handler NoContent + deleteAccount cookie username = adminsOnly cookie $ do + liftIO $ Accounts.delete dbFile (T.Username username) + pure NoContent + + listAccounts :: T.SessionCookie -> Handler [T.User] + listAccounts cookie = adminsOnly cookie $ do + liftIO $ Accounts.list dbFile + + createTrip :: T.SessionCookie -> T.Trip -> Handler NoContent + createTrip cookie trip@T.Trip{..} = + adminsAnd cookie (\T.Account{..} -> accountUsername == tripUsername) $ do + liftIO $ Trips.create dbFile trip + pure NoContent + + updateTrip :: T.SessionCookie -> T.UpdateTripRequest -> Handler NoContent + updateTrip cookie updates@T.UpdateTripRequest{..} = + adminsAnd cookie (\T.Account{..} -> accountUsername == T.tripPKUsername updateTripRequestTripPK) $ do + mTrip <- liftIO $ Trips.get dbFile updateTripRequestTripPK + case mTrip of + Nothing -> throwError err400 { errBody = "tripKey is invalid" } + Just trip@T.Trip{..} -> do + -- TODO(wpcarro): Prefer function in Trips module that does this in a + -- DB transaction. + liftIO $ Trips.delete dbFile updateTripRequestTripPK + liftIO $ Trips.create dbFile (T.updateTrip updates trip) + pure NoContent + + deleteTrip :: T.SessionCookie -> T.TripPK -> Handler NoContent + deleteTrip cookie tripPK@T.TripPK{..} = + adminsAnd cookie (\T.Account{..} -> accountUsername == tripPKUsername) $ do + liftIO $ Trips.delete dbFile tripPK + pure NoContent + + listTrips :: T.SessionCookie -> Handler [T.Trip] + listTrips cookie = do + mAccount <- liftIO $ Auth.accountFromCookie dbFile cookie + case mAccount of + Nothing -> throwError err401 { errBody = "Your session cookie is invalid. Try logging out and logging back in." } + Just T.Account{..} -> + case accountRole of + T.Admin -> liftIO $ Trips.listAll dbFile + _ -> liftIO $ Trips.list dbFile accountUsername + + login :: T.AccountCredentials + -> Handler (Headers '[Header "Set-Cookie" SetCookie] T.Session) + login (T.AccountCredentials username password) = do + mAccount <- liftIO $ Accounts.lookup dbFile username + case mAccount of + Just account@T.Account{..} -> do + mAttempts <- liftIO $ LoginAttempts.forUsername dbFile accountUsername + case mAttempts of + Nothing -> + if T.passwordsMatch password accountPassword then do + uuid <- liftIO $ Sessions.findOrCreate dbFile account + pure $ addHeader (Auth.mkCookie uuid) + T.Session{ sessionUsername = accountUsername + , sessionRole = accountRole + } + else do + liftIO $ LoginAttempts.increment dbFile username + throwError err401 { errBody = "Your credentials are invalid" } + Just attempts -> + if attempts >= 3 then + throwError err429 + else if T.passwordsMatch password accountPassword then do + uuid <- liftIO $ Sessions.findOrCreate dbFile account + pure $ addHeader (Auth.mkCookie uuid) + T.Session{ sessionUsername = accountUsername + , sessionRole = accountRole + } + else do + liftIO $ LoginAttempts.increment dbFile username + throwError err401 { errBody = "Your credentials are invalid" } + + -- In this branch, the user didn't supply a known username. + Nothing -> throwError err401 { errBody = "Your credentials are invalid" } + + logout :: T.SessionCookie + -> Handler (Headers '[Header "Set-Cookie" SetCookie] NoContent) + logout cookie = do + case Auth.uuidFromCookie cookie of + Nothing -> + pure $ addHeader Auth.emptyCookie NoContent + Just uuid -> do + liftIO $ Sessions.delete dbFile uuid + pure $ addHeader Auth.emptyCookie NoContent + + unfreezeAccount :: T.SessionCookie + -> T.UnfreezeAccountRequest + -> Handler NoContent + unfreezeAccount cookie T.UnfreezeAccountRequest{..} = + adminsAnd cookie (\T.Account{..} -> accountRole == T.Manager) $ do + liftIO $ LoginAttempts.reset dbFile unfreezeAccountRequestUsername + pure NoContent + + inviteUser :: T.SessionCookie + -> T.InviteUserRequest + -> Handler NoContent + inviteUser cookie T.InviteUserRequest{..} = adminsOnly cookie $ do + secretUUID <- liftIO $ T.InvitationSecret <$> Random.randomIO + liftIO $ Invitations.create dbFile + secretUUID + inviteUserRequestEmail + inviteUserRequestRole + res <- liftIO $ sendInviteEmail config inviteUserRequestEmail secretUUID + case res of + Left _ -> undefined + Right _ -> pure NoContent + + acceptInvitation :: T.AcceptInvitationRequest -> Handler NoContent + acceptInvitation T.AcceptInvitationRequest{..} = do + mInvitation <- liftIO $ Invitations.get dbFile acceptInvitationRequestEmail + case mInvitation of + Nothing -> throwError err404 { errBody = "No invitation for email" } + Just T.Invitation{..} -> + if invitationSecret == acceptInvitationRequestSecret then do + liftIO $ Accounts.create dbFile + acceptInvitationRequestUsername + acceptInvitationRequestPassword + invitationEmail + invitationRole + pure NoContent + else + throwError err401 { errBody = "You are not providing a valid secret" } + +run :: T.Config -> IO () +run config@T.Config{..} = + Warp.run 3000 (enforceCors $ serve (Proxy @ API) $ server config) + where + enforceCors = Cors.cors (const $ Just corsPolicy) + corsPolicy :: Cors.CorsResourcePolicy + corsPolicy = + Cors.simpleCorsResourcePolicy + { Cors.corsOrigins = Just ([cs configClient], True) + , Cors.corsMethods = Cors.simpleMethods ++ ["PUT", "PATCH", "DELETE", "OPTIONS"] + , Cors.corsRequestHeaders = Cors.simpleHeaders ++ ["Content-Type", "Authorization"] + } diff --git a/users/wpcarro/assessments/tt/src/Auth.hs b/users/wpcarro/assessments/tt/src/Auth.hs new file mode 100644 index 000000000..f1bff2325 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Auth.hs @@ -0,0 +1,64 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Auth where +-------------------------------------------------------------------------------- +import Control.Monad.IO.Class (liftIO) +import Web.Cookie +import Servant + +import qualified Data.UUID as UUID +import qualified Sessions as Sessions +import qualified Accounts as Accounts +import qualified Types as T +-------------------------------------------------------------------------------- + +-- | Return the UUID from a Session cookie. +uuidFromCookie :: T.SessionCookie -> Maybe T.SessionUUID +uuidFromCookie (T.SessionCookie cookies) = do + auth <- lookup "auth" cookies + uuid <- UUID.fromASCIIBytes auth + pure $ T.SessionUUID uuid + +-- | Attempt to return the account associated with `cookie`. +accountFromCookie :: FilePath -> T.SessionCookie -> IO (Maybe T.Account) +accountFromCookie dbFile cookie = + case uuidFromCookie cookie of + Nothing -> pure Nothing + Just uuid -> do + mSession <- Sessions.get dbFile uuid + case mSession of + Nothing -> pure Nothing + Just T.StoredSession{..} -> do + mAccount <- Accounts.lookup dbFile storedSessionUsername + case mAccount of + Nothing -> pure Nothing + Just x -> pure (Just x) + +-- | Create a new session cookie. +mkCookie :: T.SessionUUID -> SetCookie +mkCookie (T.SessionUUID uuid) = + defaultSetCookie + { setCookieName = "auth" + , setCookieValue = UUID.toASCIIBytes uuid + } + +-- | Use this to clear out the session cookie. +emptyCookie :: SetCookie +emptyCookie = + defaultSetCookie + { setCookieName = "auth" + , setCookieValue = "" + } + +-- | Throw a 401 error if the `predicate` fails. +assert :: FilePath -> T.SessionCookie -> (T.Account -> Bool) -> Handler a -> Handler a +assert dbFile cookie predicate handler = do + mRole <- liftIO $ accountFromCookie dbFile cookie + case mRole of + Nothing -> throwError err401 { errBody = "Missing valid session cookie" } + Just account -> + if predicate account then + handler + else + throwError err401 { errBody = "You are not authorized to access this resource" } diff --git a/users/wpcarro/assessments/tt/src/Email.hs b/users/wpcarro/assessments/tt/src/Email.hs new file mode 100644 index 000000000..2dac0973b --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Email.hs @@ -0,0 +1,46 @@ +{-# LANGUAGE OverloadedStrings #-} +-------------------------------------------------------------------------------- +module Email where +-------------------------------------------------------------------------------- +import Data.Text +import Data.String.Conversions (cs) +import Utils + +import qualified Mail.Hailgun as MG +import qualified Types as T +-------------------------------------------------------------------------------- + +newtype SendSuccess = SendSuccess MG.HailgunSendResponse + +data SendError + = MessageError MG.HailgunErrorMessage + | ResponseError MG.HailgunErrorResponse + +-- | Attempt to send an email with `subject` and with message, `body`. +send :: Text + -> Text + -> Text + -> T.Email + -> IO (Either SendError SendSuccess) +send apiKey subject body (T.Email to) = do + case mkMsg of + Left e -> pure $ Left (MessageError e) + Right x -> do + res <- MG.sendEmail ctx x + case res of + Left e -> pure $ Left (ResponseError e) + Right y -> pure $ Right (SendSuccess y) + where + ctx = MG.HailgunContext { MG.hailgunDomain = "sandboxda5038873f924b50af2f82a0f05cffdf.mailgun.org" + , MG.hailgunApiKey = cs apiKey + , MG.hailgunProxy = Nothing + } + mkMsg = MG.hailgunMessage + subject + (body |> cs |> MG.TextOnly) + "mailgun@sandboxda5038873f924b50af2f82a0f05cffdf.mailgun.org" + (MG.MessageRecipients { MG.recipientsTo = [cs to] + , MG.recipientsCC = [] + , MG.recipientsBCC = [] + }) + [] diff --git a/users/wpcarro/assessments/tt/src/Invitations.hs b/users/wpcarro/assessments/tt/src/Invitations.hs new file mode 100644 index 000000000..0c700470f --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Invitations.hs @@ -0,0 +1,21 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module Invitations where +-------------------------------------------------------------------------------- +import Database.SQLite.Simple + +import qualified Types as T +-------------------------------------------------------------------------------- + +create :: FilePath -> T.InvitationSecret -> T.Email -> T.Role -> IO () +create dbFile secret email role = withConnection dbFile $ \conn -> do + execute conn "INSERT INTO Invitations (email,role,secret) VALUES (?,?,?)" + (email, role, secret) + +get :: FilePath -> T.Email -> IO (Maybe T.Invitation) +get dbFile email = withConnection dbFile $ \conn -> do + res <- query conn "SELECT email,role,secret FROM Invitations WHERE email = ?" (Only email) + case res of + [x] -> pure (Just x) + _ -> pure Nothing diff --git a/users/wpcarro/assessments/tt/src/LoginAttempts.hs b/users/wpcarro/assessments/tt/src/LoginAttempts.hs new file mode 100644 index 000000000..d78e12e3f --- /dev/null +++ b/users/wpcarro/assessments/tt/src/LoginAttempts.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module LoginAttempts where +-------------------------------------------------------------------------------- +import Database.SQLite.Simple + +import qualified Types as T +-------------------------------------------------------------------------------- + +reset :: FilePath -> T.Username -> IO () +reset dbFile username = withConnection dbFile $ \conn -> + execute conn "UPDATE LoginAttempts SET numAttempts = 0 WHERE username = ?" + (Only username) + +-- | Attempt to return the number of failed login attempts for +-- `username`. Returns a Maybe in case `username` doesn't exist. +forUsername :: FilePath -> T.Username -> IO (Maybe Integer) +forUsername dbFile username = withConnection dbFile $ \conn -> do + res <- query conn "SELECT username,numAttempts FROM LoginAttempts WHERE username = ?" + (Only username) + case res of + [T.LoginAttempt{..}] -> pure (Just loginAttemptNumAttempts) + _ -> pure Nothing + +-- | INSERT a failed login attempt for `username` or UPDATE an existing entry. +increment :: FilePath -> T.Username -> IO () +increment dbFile username = withConnection dbFile $ \conn -> + execute conn "INSERT INTO LoginAttempts (username,numAttempts) VALUES (?,?) ON CONFLICT (username) DO UPDATE SET numAttempts = numAttempts + 1" + (username, 1 :: Integer) diff --git a/users/wpcarro/assessments/tt/src/Main.hs b/users/wpcarro/assessments/tt/src/Main.hs new file mode 100644 index 000000000..9df423206 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Main.hs @@ -0,0 +1,13 @@ +-------------------------------------------------------------------------------- +module Main where +-------------------------------------------------------------------------------- +import qualified App +import qualified System.Envy as Envy +-------------------------------------------------------------------------------- + +main :: IO () +main = do + mEnv <- Envy.decodeEnv + case mEnv of + Left err -> putStrLn err + Right env -> App.run env diff --git a/users/wpcarro/assessments/tt/src/PendingAccounts.hs b/users/wpcarro/assessments/tt/src/PendingAccounts.hs new file mode 100644 index 000000000..a555185fa --- /dev/null +++ b/users/wpcarro/assessments/tt/src/PendingAccounts.hs @@ -0,0 +1,32 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +-------------------------------------------------------------------------------- +module PendingAccounts where +-------------------------------------------------------------------------------- +import Database.SQLite.Simple + +import qualified Types as T +-------------------------------------------------------------------------------- + +create :: FilePath + -> T.RegistrationSecret + -> T.Username + -> T.ClearTextPassword + -> T.Role + -> T.Email + -> IO () +create dbFile secret username password role email = withConnection dbFile $ \conn -> do + hashed <- T.hashPassword password + execute conn "INSERT INTO PendingAccounts (secret,username,password,role,email) VALUES (?,?,?,?,?)" + (secret, username, hashed, role, email) + +get :: FilePath -> T.Username -> IO (Maybe T.PendingAccount) +get dbFile username = withConnection dbFile $ \conn -> do + res <- query conn "SELECT secret,username,password,role,email FROM PendingAccounts WHERE username = ?" (Only username) + case res of + [x] -> pure (Just x) + _ -> pure Nothing + +delete :: FilePath -> T.Username -> IO () +delete dbFile username = withConnection dbFile $ \conn -> + execute conn "DELETE FROM PendingAccounts WHERE username = ?" (Only username) diff --git a/users/wpcarro/assessments/tt/src/Sessions.hs b/users/wpcarro/assessments/tt/src/Sessions.hs new file mode 100644 index 000000000..713059a38 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Sessions.hs @@ -0,0 +1,74 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +-------------------------------------------------------------------------------- +module Sessions where +-------------------------------------------------------------------------------- +import Database.SQLite.Simple + +import qualified Data.Time.Clock as Clock +import qualified Types as T +import qualified System.Random as Random +-------------------------------------------------------------------------------- + +-- | Return True if `session` was created at most three hours ago. +isValid :: T.StoredSession -> IO Bool +isValid session = do + t1 <- Clock.getCurrentTime + let t0 = T.storedSessionTsCreated session in + pure $ Clock.diffUTCTime t1 t0 <= 3 * 60 * 60 + +-- | Lookup the session by UUID. +get :: FilePath -> T.SessionUUID -> IO (Maybe T.StoredSession) +get dbFile uuid = withConnection dbFile $ \conn -> do + res <- query conn "SELECT uuid,username,tsCreated FROM Sessions WHERE uuid = ?" (Only uuid) + case res of + [x] -> pure (Just x) + _ -> pure Nothing + +-- | Lookup the session stored under `username` in `dbFile`. +find :: FilePath -> T.Username -> IO (Maybe T.StoredSession) +find dbFile username = withConnection dbFile $ \conn -> do + res <- query conn "SELECT uuid,username,tsCreated FROM Sessions WHERE username = ?" (Only username) + case res of + [x] -> pure (Just x) + _ -> pure Nothing + +-- | Create a session under the `username` key in `dbFile`. +create :: FilePath -> T.Username -> IO T.SessionUUID +create dbFile username = withConnection dbFile $ \conn -> do + now <- Clock.getCurrentTime + uuid <- Random.randomIO + execute conn "INSERT INTO Sessions (uuid,username,tsCreated) VALUES (?,?,?)" + (T.SessionUUID uuid, username, now) + pure (T.SessionUUID uuid) + +-- | Reset the tsCreated field to the current time to ensure the token is valid. +refresh :: FilePath -> T.SessionUUID -> IO () +refresh dbFile uuid = withConnection dbFile $ \conn -> do + now <- Clock.getCurrentTime + execute conn "UPDATE Sessions SET tsCreated = ? WHERE uuid = ?" + (now, uuid) + pure () + +-- | Delete the session under `username` from `dbFile`. +delete :: FilePath -> T.SessionUUID -> IO () +delete dbFile uuid = withConnection dbFile $ \conn -> + execute conn "DELETE FROM Sessions WHERE uuid = ?" (Only uuid) + +-- | Find or create a session in the Sessions table. If a session exists, +-- refresh the token's validity. +findOrCreate :: FilePath -> T.Account -> IO T.SessionUUID +findOrCreate dbFile account = + let username = T.accountUsername account in do + mSession <- find dbFile username + case mSession of + Nothing -> create dbFile username + Just session -> + let uuid = T.storedSessionUUID session in do + refresh dbFile uuid + pure uuid + +-- | Return a list of all sessions in the Sessions table. +list :: FilePath -> IO [T.StoredSession] +list dbFile = withConnection dbFile $ \conn -> + query_ conn "SELECT uuid,username,tsCreated FROM Sessions" diff --git a/users/wpcarro/assessments/tt/src/Trips.hs b/users/wpcarro/assessments/tt/src/Trips.hs new file mode 100644 index 000000000..f90740363 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Trips.hs @@ -0,0 +1,42 @@ +{-# LANGUAGE OverloadedStrings #-} +-------------------------------------------------------------------------------- +module Trips where +-------------------------------------------------------------------------------- +import Database.SQLite.Simple +import Utils + +import qualified Types as T +-------------------------------------------------------------------------------- + +-- | Create a new `trip` in `dbFile`. +create :: FilePath -> T.Trip -> IO () +create dbFile trip = withConnection dbFile $ \conn -> + execute conn "INSERT INTO Trips (username,destination,startDate,endDate,comment) VALUES (?,?,?,?,?)" + (trip |> T.tripFields) + +-- | Attempt to get the trip record from `dbFile` under `tripKey`. +get :: FilePath -> T.TripPK -> IO (Maybe T.Trip) +get dbFile tripKey = withConnection dbFile $ \conn -> do + res <- query conn "SELECT username,destination,startDate,endDate,comment FROM Trips WHERE username = ? AND destination = ? AND startDate = ? LIMIT 1" + (T.tripPKFields tripKey) + case res of + [x] -> pure (Just x) + _ -> pure Nothing + +-- | Delete a trip from `dbFile` using its `tripKey` Primary Key. +delete :: FilePath -> T.TripPK -> IO () +delete dbFile tripKey = + withConnection dbFile $ \conn -> do + execute conn "DELETE FROM Trips WHERE username = ? AND destination = ? and startDate = ?" + (T.tripPKFields tripKey) + +-- | Return a list of all of the trips in `dbFile`. +listAll :: FilePath -> IO [T.Trip] +listAll dbFile = withConnection dbFile $ \conn -> + query_ conn "SELECT username,destination,startDate,endDate,comment FROM Trips ORDER BY date(startDate) ASC" + +-- | Return a list of all of the trips in `dbFile`. +list :: FilePath -> T.Username -> IO [T.Trip] +list dbFile username = withConnection dbFile $ \conn -> + query conn "SELECT username,destination,startDate,endDate,comment FROM Trips WHERE username = ? ORDER BY date(startDate) ASC" + (Only username) diff --git a/users/wpcarro/assessments/tt/src/Types.hs b/users/wpcarro/assessments/tt/src/Types.hs new file mode 100644 index 000000000..6b06a3969 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Types.hs @@ -0,0 +1,544 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE NamedFieldPuns #-} +-------------------------------------------------------------------------------- +module Types where +-------------------------------------------------------------------------------- +import Data.Aeson +import Utils +import Data.Text +import Data.Typeable +import Database.SQLite.Simple +import Database.SQLite.Simple.Ok +import Database.SQLite.Simple.FromField +import Database.SQLite.Simple.ToField +import GHC.Generics +import Web.Cookie +import Servant.API +import System.Envy (FromEnv, fromEnv, env) +import Crypto.Random.Types (MonadRandom) + +import qualified Data.Time.Calendar as Calendar +import qualified Crypto.KDF.BCrypt as BC +import qualified Data.Time.Clock as Clock +import qualified Data.ByteString.Char8 as B +import qualified Data.ByteString as BS +import qualified Data.Text.Encoding as TE +import qualified Data.Maybe as M +import qualified Data.UUID as UUID +-------------------------------------------------------------------------------- + +-- | Top-level application configuration. +data Config = Config + { mailgunAPIKey :: Text + , dbFile :: FilePath + , configClient :: Text + , configServer :: Text + } deriving (Eq, Show) + +instance FromEnv Config where + fromEnv _ = do + mailgunAPIKey <- env "MAILGUN_API_KEY" + dbFile <- env "DB_FILE" + configClient <- env "CLIENT" + configServer <- env "SERVER" + pure Config {..} + +-- TODO(wpcarro): Properly handle NULL for columns like profilePicture. +forNewtype :: (Typeable b) => (Text -> b) -> FieldParser b +forNewtype wrapper y = + case fieldData y of + (SQLText x) -> Ok (wrapper x) + x -> returnError ConversionFailed y ("We expected SQLText, but we received: " ++ show x) + +newtype Username = Username Text + deriving (Eq, Show, Generic) + +instance ToJSON Username +instance FromJSON Username + +instance ToField Username where + toField (Username x) = SQLText x + +instance FromField Username where + fromField = forNewtype Username + +newtype HashedPassword = HashedPassword BS.ByteString + deriving (Eq, Show, Generic) + +instance ToField HashedPassword where + toField (HashedPassword x) = SQLText (TE.decodeUtf8 x) + +instance FromField HashedPassword where + fromField y = + case fieldData y of + (SQLText x) -> x |> TE.encodeUtf8 |> HashedPassword |> Ok + x -> returnError ConversionFailed y ("We expected SQLText, but we received: " ++ show x) + +newtype ClearTextPassword = ClearTextPassword Text + deriving (Eq, Show, Generic) + +instance ToJSON ClearTextPassword +instance FromJSON ClearTextPassword + +instance ToField ClearTextPassword where + toField (ClearTextPassword x) = SQLText x + +instance FromField ClearTextPassword where + fromField = forNewtype ClearTextPassword + +newtype Email = Email Text + deriving (Eq, Show, Generic) + +instance ToJSON Email +instance FromJSON Email + +instance ToField Email where + toField (Email x) = SQLText x + +instance FromField Email where + fromField = forNewtype Email + +data Role = RegularUser | Manager | Admin + deriving (Eq, Show, Generic) + +instance ToJSON Role where + toJSON RegularUser = "user" + toJSON Manager = "manager" + toJSON Admin = "admin" + +instance FromJSON Role where + parseJSON = withText "Role" $ \x -> + case x of + "user" -> pure RegularUser + "manager" -> pure Manager + "admin" -> pure Admin + _ -> fail "Expected \"user\" or \"manager\" or \"admin\"" + +instance ToField Role where + toField RegularUser = SQLText "user" + toField Manager = SQLText "manager" + toField Admin = SQLText "admin" + +instance FromField Role where + fromField y = + case fieldData y of + (SQLText "user") -> Ok RegularUser + (SQLText "manager") -> Ok Manager + (SQLText "admin") -> Ok Admin + x -> returnError ConversionFailed y ("We expected user, manager, admin, but we received: " ++ show x) + +-- TODO(wpcarro): Prefer Data.ByteString instead of Text +newtype ProfilePicture = ProfilePicture Text + deriving (Eq, Show, Generic) + +instance ToJSON ProfilePicture +instance FromJSON ProfilePicture + +instance ToField ProfilePicture where + toField (ProfilePicture x) = SQLText x + +instance FromField ProfilePicture where + fromField = forNewtype ProfilePicture + +data Account = Account + { accountUsername :: Username + , accountPassword :: HashedPassword + , accountEmail :: Email + , accountRole :: Role + , accountProfilePicture :: Maybe ProfilePicture + } deriving (Eq, Show, Generic) + +-- | Return a tuple with all of the fields for an Account record to use for SQL. +accountFields :: Account -> (Username, HashedPassword, Email, Role, Maybe ProfilePicture) +accountFields (Account {..}) + = ( accountUsername + , accountPassword + , accountEmail + , accountRole + , accountProfilePicture + ) + +instance FromRow Account where + fromRow = do + accountUsername <- field + accountPassword <- field + accountEmail <- field + accountRole <- field + accountProfilePicture <- field + pure Account{..} + +data Session = Session + { sessionUsername :: Username + , sessionRole :: Role + } deriving (Eq, Show) + +instance ToJSON Session where + toJSON (Session username role) = + object [ "username" .= username + , "role" .= role + ] + +newtype Comment = Comment Text + deriving (Eq, Show, Generic) + +instance ToJSON Comment +instance FromJSON Comment + +instance ToField Comment where + toField (Comment x) = SQLText x + +instance FromField Comment where + fromField = forNewtype Comment + +newtype Destination = Destination Text + deriving (Eq, Show, Generic) + +instance ToJSON Destination +instance FromJSON Destination + +instance ToField Destination where + toField (Destination x) = SQLText x + +instance FromField Destination where + fromField = forNewtype Destination + +newtype Year = Year Integer deriving (Eq, Show) +newtype Month = Month Integer deriving (Eq, Show) +newtype Day = Day Integer deriving (Eq, Show) +data Date = Date + { dateYear :: Year + , dateMonth :: Month + , dateDay :: Day + } deriving (Eq, Show) + +data Trip = Trip + { tripUsername :: Username + , tripDestination :: Destination + , tripStartDate :: Calendar.Day + , tripEndDate :: Calendar.Day + , tripComment :: Comment + } deriving (Eq, Show, Generic) + +instance FromRow Trip where + fromRow = do + tripUsername <- field + tripDestination <- field + tripStartDate <- field + tripEndDate <- field + tripComment <- field + pure Trip{..} + +-- | The fields used as the Primary Key for a Trip entry. +data TripPK = TripPK + { tripPKUsername :: Username + , tripPKDestination :: Destination + , tripPKStartDate :: Calendar.Day + } deriving (Eq, Show, Generic) + +tripPKFields :: TripPK -> (Username, Destination, Calendar.Day) +tripPKFields (TripPK{..}) + = (tripPKUsername, tripPKDestination, tripPKStartDate) + +instance FromJSON TripPK where + parseJSON = withObject "TripPK" $ \x -> do + tripPKUsername <- x .: "username" + tripPKDestination <- x .: "destination" + tripPKStartDate <- x .: "startDate" + pure TripPK{..} + +-- | Return the tuple representation of a Trip record for SQL. +tripFields :: Trip + -> (Username, Destination, Calendar.Day, Calendar.Day, Comment) +tripFields (Trip{..}) + = ( tripUsername + , tripDestination + , tripStartDate + , tripEndDate + , tripComment + ) + +instance ToJSON Trip where + toJSON (Trip username destination startDate endDate comment) = + object [ "username" .= username + , "destination" .= destination + , "startDate" .= startDate + , "endDate" .= endDate + , "comment" .= comment + ] + +instance FromJSON Trip where + parseJSON = withObject "Trip" $ \x -> do + tripUsername <- x .: "username" + tripDestination <- x .: "destination" + tripStartDate <- x .: "startDate" + tripEndDate <- x .: "endDate" + tripComment <- x .: "comment" + pure Trip{..} + +-- | Users and Accounts both refer to the same underlying entities; however, +-- Users model the user-facing Account details, hiding sensitive details like +-- passwords and emails. +data User = User + { userUsername :: Username + , userProfilePicture :: Maybe ProfilePicture + , userRole :: Role + } deriving (Eq, Show, Generic) + +instance ToJSON User where + toJSON (User username profilePicture role) = + object [ "username" .= username + , "profilePicture" .= profilePicture + , "role" .= role + ] + +userFromAccount :: Account -> User +userFromAccount account = + User { userUsername = accountUsername account + , userProfilePicture = accountProfilePicture account + , userRole = accountRole account + } + +-- | This is the data that a user needs to supply to authenticate with the +-- application. +data AccountCredentials = AccountCredentials + { accountCredentialsUsername :: Username + , accountCredentialsPassword :: ClearTextPassword + } deriving (Eq, Show, Generic) + +instance FromJSON AccountCredentials where + parseJSON = withObject "AccountCredentials" $ \x -> do + accountCredentialsUsername <- x.: "username" + accountCredentialsPassword <- x.: "password" + pure AccountCredentials{..} + + +-- | Hash password `x`. +hashPassword :: (MonadRandom m) => ClearTextPassword -> m HashedPassword +hashPassword (ClearTextPassword x) = do + hashed <- BC.hashPassword 12 (x |> unpack |> B.pack) + pure $ HashedPassword hashed + +-- | Return True if the cleartext password matches the hashed password. +passwordsMatch :: ClearTextPassword -> HashedPassword -> Bool +passwordsMatch (ClearTextPassword clear) (HashedPassword hashed) = + BC.validatePassword (clear |> unpack |> B.pack) hashed + +data CreateAccountRequest = CreateAccountRequest + { createAccountRequestUsername :: Username + , createAccountRequestPassword :: ClearTextPassword + , createAccountRequestEmail :: Email + , createAccountRequestRole :: Role + } deriving (Eq, Show) + +instance FromJSON CreateAccountRequest where + parseJSON = withObject "CreateAccountRequest" $ \x -> do + createAccountRequestUsername <- x .: "username" + createAccountRequestPassword <- x .: "password" + createAccountRequestEmail <- x .: "email" + createAccountRequestRole <- x .: "role" + pure $ CreateAccountRequest{..} + +createAccountRequestFields :: CreateAccountRequest + -> (Username, ClearTextPassword, Email, Role) +createAccountRequestFields CreateAccountRequest{..} = + ( createAccountRequestUsername + , createAccountRequestPassword + , createAccountRequestEmail + , createAccountRequestRole + ) + +newtype SessionUUID = SessionUUID UUID.UUID + deriving (Eq, Show, Generic) + +instance FromField SessionUUID where + fromField y = + case fieldData y of + (SQLText x) -> + case UUID.fromText x of + Nothing -> returnError ConversionFailed y ("Could not convert to UUID: " ++ show x) + Just uuid -> Ok $ SessionUUID uuid + _ -> returnError ConversionFailed y "Expected SQLText for SessionUUID, but we received" + +instance ToField SessionUUID where + toField (SessionUUID uuid) = + uuid |> UUID.toText |> SQLText + +data StoredSession = StoredSession + { storedSessionUUID :: SessionUUID + , storedSessionUsername :: Username + , storedSessionTsCreated :: Clock.UTCTime + } deriving (Eq, Show, Generic) + +instance FromRow StoredSession where + fromRow = do + storedSessionUUID <- field + storedSessionUsername <- field + storedSessionTsCreated <- field + pure StoredSession {..} + +data LoginAttempt = LoginAttempt + { loginAttemptUsername :: Username + , loginAttemptNumAttempts :: Integer + } deriving (Eq, Show) + +instance FromRow LoginAttempt where + fromRow = do + loginAttemptUsername <- field + loginAttemptNumAttempts <- field + pure LoginAttempt {..} + +newtype SessionCookie = SessionCookie Cookies + +instance FromHttpApiData SessionCookie where + parseHeader x = + x |> parseCookies |> SessionCookie |> pure + parseQueryParam x = + x |> TE.encodeUtf8 |> parseCookies |> SessionCookie |> pure + +newtype RegistrationSecret = RegistrationSecret UUID.UUID + deriving (Eq, Show, Generic) + +instance FromHttpApiData RegistrationSecret where + parseQueryParam x = + case UUID.fromText x of + Nothing -> Left x + Just uuid -> Right (RegistrationSecret uuid) + +instance FromField RegistrationSecret where + fromField y = + case fieldData y of + (SQLText x) -> + case UUID.fromText x of + Nothing -> returnError ConversionFailed y ("Could not convert text to UUID: " ++ show x) + Just uuid -> Ok $ RegistrationSecret uuid + _ -> returnError ConversionFailed y "Field data is not SQLText, which is what we expect" + +instance ToField RegistrationSecret where + toField (RegistrationSecret secretUUID) = + secretUUID |> UUID.toText |> SQLText + +instance FromJSON RegistrationSecret + +data VerifyAccountRequest = VerifyAccountRequest + { verifyAccountRequestUsername :: Username + , verifyAccountRequestSecret :: RegistrationSecret + } deriving (Eq, Show) + +instance FromJSON VerifyAccountRequest where + parseJSON = withObject "VerifyAccountRequest" $ \x -> do + verifyAccountRequestUsername <- x .: "username" + verifyAccountRequestSecret <- x .: "secret" + pure VerifyAccountRequest{..} + +data PendingAccount = PendingAccount + { pendingAccountSecret :: RegistrationSecret + , pendingAccountUsername :: Username + , pendingAccountPassword :: HashedPassword + , pendingAccountRole :: Role + , pendingAccountEmail :: Email + } deriving (Eq, Show) + +instance FromRow PendingAccount where + fromRow = do + pendingAccountSecret <- field + pendingAccountUsername <- field + pendingAccountPassword <- field + pendingAccountRole <- field + pendingAccountEmail <- field + pure PendingAccount {..} + +data UpdateTripRequest = UpdateTripRequest + { updateTripRequestTripPK :: TripPK + , updateTripRequestDestination :: Maybe Destination + , updateTripRequestStartDate :: Maybe Calendar.Day + , updateTripRequestEndDate :: Maybe Calendar.Day + , updateTripRequestComment :: Maybe Comment + } deriving (Eq, Show) + +instance FromJSON UpdateTripRequest where + parseJSON = withObject "UpdateTripRequest" $ \x -> do + updateTripRequestTripPK <- x .: "tripKey" + -- the following four fields might not be present + updateTripRequestDestination <- x .:? "destination" + updateTripRequestStartDate <- x .:? "startDate" + updateTripRequestEndDate <- x .:? "endDate" + updateTripRequestComment <- x .:? "comment" + pure UpdateTripRequest{..} + +-- | Apply the updates in the UpdateTripRequest to Trip. +updateTrip :: UpdateTripRequest -> Trip -> Trip +updateTrip UpdateTripRequest{..} Trip{..} = Trip + { tripUsername = tripUsername + , tripDestination = M.fromMaybe tripDestination updateTripRequestDestination + , tripStartDate = M.fromMaybe tripStartDate updateTripRequestStartDate + , tripEndDate = M.fromMaybe tripEndDate updateTripRequestEndDate + , tripComment = M.fromMaybe tripComment updateTripRequestComment + } + +data UnfreezeAccountRequest = UnfreezeAccountRequest + { unfreezeAccountRequestUsername :: Username + } deriving (Eq, Show) + +instance FromJSON UnfreezeAccountRequest where + parseJSON = withObject "UnfreezeAccountRequest" $ \x -> do + unfreezeAccountRequestUsername <- x .: "username" + pure UnfreezeAccountRequest{..} + +data InviteUserRequest = InviteUserRequest + { inviteUserRequestEmail :: Email + , inviteUserRequestRole :: Role + } deriving (Eq, Show) + +instance FromJSON InviteUserRequest where + parseJSON = withObject "InviteUserRequest" $ \x -> do + inviteUserRequestEmail <- x .: "email" + inviteUserRequestRole <- x .: "role" + pure InviteUserRequest{..} + +newtype InvitationSecret = InvitationSecret UUID.UUID + deriving (Eq, Show, Generic) + +instance ToJSON InvitationSecret +instance FromJSON InvitationSecret + +instance ToField InvitationSecret where + toField (InvitationSecret secretUUID) = + secretUUID |> UUID.toText |> SQLText + +instance FromField InvitationSecret where + fromField y = + case fieldData y of + (SQLText x) -> + case UUID.fromText x of + Nothing -> returnError ConversionFailed y ("Could not convert text to UUID: " ++ show x) + Just z -> Ok $ InvitationSecret z + _ -> returnError ConversionFailed y "Field data is not SQLText, which is what we expect" + +data Invitation = Invitation + { invitationEmail :: Email + , invitationRole :: Role + , invitationSecret :: InvitationSecret + } deriving (Eq, Show) + +instance FromRow Invitation where + fromRow = Invitation <$> field + <*> field + <*> field + +data AcceptInvitationRequest = AcceptInvitationRequest + { acceptInvitationRequestUsername :: Username + , acceptInvitationRequestPassword :: ClearTextPassword + , acceptInvitationRequestEmail :: Email + , acceptInvitationRequestSecret :: InvitationSecret + } deriving (Eq, Show) + +instance FromJSON AcceptInvitationRequest where + parseJSON = withObject "AcceptInvitationRequest" $ \x -> do + acceptInvitationRequestUsername <- x .: "username" + acceptInvitationRequestPassword <- x .: "password" + acceptInvitationRequestEmail <- x .: "email" + acceptInvitationRequestSecret <- x .: "secret" + pure AcceptInvitationRequest{..} diff --git a/users/wpcarro/assessments/tt/src/Utils.hs b/users/wpcarro/assessments/tt/src/Utils.hs new file mode 100644 index 000000000..48c33af07 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/Utils.hs @@ -0,0 +1,9 @@ +-------------------------------------------------------------------------------- +module Utils where +-------------------------------------------------------------------------------- +import Data.Function ((&)) +-------------------------------------------------------------------------------- + +-- | Prefer this operator to the ampersand for stylistic reasons. +(|>) :: a -> (a -> b) -> b +(|>) = (&) diff --git a/users/wpcarro/assessments/tt/src/init.sql b/users/wpcarro/assessments/tt/src/init.sql new file mode 100644 index 000000000..b42753ae5 --- /dev/null +++ b/users/wpcarro/assessments/tt/src/init.sql @@ -0,0 +1,67 @@ +-- Run `.read init.sql` from within a SQLite3 REPL to initialize the tables we +-- need for this application. This will erase all current entries, so use with +-- caution. +-- Make sure to set `PRAGMA foreign_keys = on;` when transacting with the +-- database. + +BEGIN TRANSACTION; + +DROP TABLE IF EXISTS Accounts; +DROP TABLE IF EXISTS Trips; +DROP TABLE IF EXISTS Sessions; +DROP TABLE IF EXISTS LoginAttempts; +DROP TABLE IF EXISTS PendingAccounts; +DROP TABLE IF EXISTS Invitations; + +CREATE TABLE Accounts ( + username TEXT CHECK(LENGTH(username) > 0) NOT NULL, + password TEXT CHECK(LENGTH(password) > 0) NOT NULL, + email TEXT CHECK(LENGTH(email) > 0) NOT NULL UNIQUE, + role TEXT CHECK(role IN ('user', 'manager', 'admin')) NOT NULL, + profilePicture BLOB, + PRIMARY KEY (username) +); + +CREATE TABLE Trips ( + username TEXT NOT NULL, + destination TEXT CHECK(LENGTH(destination) > 0) NOT NULL, + startDate TEXT CHECK(LENGTH(startDate) == 10) NOT NULL, -- 'YYYY-MM-DD' + endDate TEXT CHECK(LENGTH(endDate) == 10) NOT NULL, -- 'YYYY-MM-DD' + comment TEXT NOT NULL, + PRIMARY KEY (username, destination, startDate), + FOREIGN KEY (username) REFERENCES Accounts ON DELETE CASCADE +); + +CREATE TABLE Sessions ( + uuid TEXT CHECK(LENGTH(uuid) == 36) NOT NULL, + username TEXT NOT NULL UNIQUE, + -- TODO(wpcarro): Add a LENGTH CHECK here + tsCreated TEXT NOT NULL, -- 'YYYY-MM-DD HH:MM:SS' + PRIMARY KEY (uuid), + FOREIGN KEY (username) REFERENCES Accounts ON DELETE CASCADE +); + +CREATE TABLE LoginAttempts ( + username TEXT NOT NULL UNIQUE, + numAttempts INTEGER NOT NULL, + PRIMARY KEY (username), + FOREIGN KEY (username) REFERENCES Accounts ON DELETE CASCADE +); + +CREATE TABLE PendingAccounts ( + secret TEXT CHECK(LENGTH(secret) == 36) NOT NULL, + username TEXT CHECK(LENGTH(username) > 0) NOT NULL, + password TEXT CHECK(LENGTH(password) > 0) NOT NULL, + role TEXT CHECK(role IN ('user', 'manager', 'admin')) NOT NULL, + email TEXT CHECK(LENGTH(email) > 0) NOT NULL UNIQUE, + PRIMARY KEY (username) +); + +CREATE TABLE Invitations ( + email TEXT CHECK(LENGTH(email) > 0) NOT NULL UNIQUE, + role TEXT CHECK(role IN ('user', 'manager', 'admin')) NOT NULL, + secret TEXT CHECK(LENGTH(secret) == 36) NOT NULL, + PRIMARY KEY (email) +); + +COMMIT; diff --git a/users/wpcarro/assessments/tt/tests/create-accounts.sh b/users/wpcarro/assessments/tt/tests/create-accounts.sh new file mode 100755 index 000000000..8c2a66bc8 --- /dev/null +++ b/users/wpcarro/assessments/tt/tests/create-accounts.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env sh + +# This script populates the Accounts table over HTTP. + +http POST :3000/accounts \ + username=mimi \ + password=testing \ + email=miriamwright@google.com \ + role=user + +http POST :3000/accounts \ + username=bill \ + password=testing \ + email=wpcarro@gmail.com \ + role=manager + +http POST :3000/accounts \ + username=wpcarro \ + password=testing \ + email=wpcarro@google.com \ + role=admin diff --git a/users/wpcarro/assessments/tt/todo.org b/users/wpcarro/assessments/tt/todo.org new file mode 100644 index 000000000..39592d048 --- /dev/null +++ b/users/wpcarro/assessments/tt/todo.org @@ -0,0 +1,18 @@ +* TODO Users must be able to create an account +* TODO Users must verify their account by email +* TODO Support federated login with Google +* TODO Users must be able to authenticate and login +* TODO Define three roles: user, manager, admin +* TODO Users can add trips +* TODO Users can edit trips +* TODO Users can delete trips +* TODO Users can filter trips +* TODO Support all actions via the REST API +* TODO Block users after three failed authentication attempts +* TODO Only admins and managers can unblock blocked login attempts +* TODO Add unit tests +* TODO Add E2E tests +* TODO Pull user profile pictures using Gravatar +* TODO Allow users to change their profile picture +* TODO Admins should be allowed to invite new users via email +* TODO Allow users to print their travel itineraries diff --git a/users/wpcarro/boilerplate/README.md b/users/wpcarro/boilerplate/README.md new file mode 100644 index 000000000..0ff4afa5b --- /dev/null +++ b/users/wpcarro/boilerplate/README.md @@ -0,0 +1,21 @@ +# Boilerplate + +Storing some boilerplate code to help me reduce the time it takes me to develop +and deploy applications. + +## Usage + +Let's say that you would like to create a game for +`sandbox.wpcarro.dev/game`. We will create a new TypeScript project with the +following: + +```shell +$ cp ~/briefcase/boilerplate/typescript ~/briefcase/website/sandbox/game +``` + +This initializes the project. To start developing, run: + +```shell +$ nix-shell +$ yarn run dev +``` diff --git a/users/wpcarro/boilerplate/clojure/.envrc b/users/wpcarro/boilerplate/clojure/.envrc new file mode 100644 index 000000000..a4a62da52 --- /dev/null +++ b/users/wpcarro/boilerplate/clojure/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/boilerplate/clojure/.gitignore b/users/wpcarro/boilerplate/clojure/.gitignore new file mode 100644 index 000000000..f24c5e393 --- /dev/null +++ b/users/wpcarro/boilerplate/clojure/.gitignore @@ -0,0 +1,4 @@ +/.lein-repl-history +/target +/? +/.nrepl-port \ No newline at end of file diff --git a/users/wpcarro/boilerplate/clojure/README.md b/users/wpcarro/boilerplate/clojure/README.md new file mode 100644 index 000000000..2eec7f75e --- /dev/null +++ b/users/wpcarro/boilerplate/clojure/README.md @@ -0,0 +1,33 @@ +# Clojure Boilerplate + +This boilerplate uses `lein` to manage the project. + +## Files to change + +To use this boilerplate, run the following in a shell: + +```shell +$ cp ~/briefcase/boilerplate/clojure path/to/new-project +``` + +After running the above command, change the following files to remove the +placeholder values: + +- `README.md`: Change the title; change the description; drop "Files to change"; + keep "Getting started" +- `project.clj`: Change title +- `src/main.clj`: Change `:doc`; drop `main/foo` + +## Getting started + +From a shell, run: + +```shell +$ lein repl +``` + +From Emacs, navigate to a source code buffer and run: + +``` +M-x cider-jack-in +``` diff --git a/users/wpcarro/boilerplate/clojure/project.clj b/users/wpcarro/boilerplate/clojure/project.clj new file mode 100644 index 000000000..54e34eab7 --- /dev/null +++ b/users/wpcarro/boilerplate/clojure/project.clj @@ -0,0 +1,2 @@ +(defproject boilerplate "0.0.1" + :dependencies [[org.clojure/clojure "1.8.0"]]) diff --git a/users/wpcarro/boilerplate/clojure/shell.nix b/users/wpcarro/boilerplate/clojure/shell.nix new file mode 100644 index 000000000..efa854422 --- /dev/null +++ b/users/wpcarro/boilerplate/clojure/shell.nix @@ -0,0 +1,8 @@ +let + briefcase = import {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs; [ + leiningen + ]; +} diff --git a/users/wpcarro/boilerplate/clojure/src/main.clj b/users/wpcarro/boilerplate/clojure/src/main.clj new file mode 100644 index 000000000..f6b60dba4 --- /dev/null +++ b/users/wpcarro/boilerplate/clojure/src/main.clj @@ -0,0 +1,8 @@ +(ns ^{:doc "Top-level module." + :author "William Carroll"} + main) + +(declare main) + +(defn foo [a b] + (+ a b)) diff --git a/users/wpcarro/boilerplate/elm/.envrc b/users/wpcarro/boilerplate/elm/.envrc new file mode 100644 index 000000000..a4a62da52 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/boilerplate/elm/.gitignore b/users/wpcarro/boilerplate/elm/.gitignore new file mode 100644 index 000000000..1cb4f3034 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/.gitignore @@ -0,0 +1,3 @@ +/elm-stuff +/Main.min.js +/output.css diff --git a/users/wpcarro/boilerplate/elm/README.md b/users/wpcarro/boilerplate/elm/README.md new file mode 100644 index 000000000..04804ad94 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/README.md @@ -0,0 +1,18 @@ +# Elm + +Elm has one of the best developer experiences that I'm aware of. The error +messages are helpful and the entire experience is optimized to improve the ease +of writing web applications. + +## Developing + +If you're interested in contributing, the following will create an environment +in which you can develop: + +```shell +$ nix-shell +$ npx tailwindcss build index.css -o output.css +$ elm-live -- src/Main.elm --output=Main.min.js +``` + +You can now view your web client at `http://localhost:8000`! diff --git a/users/wpcarro/boilerplate/elm/elm.json b/users/wpcarro/boilerplate/elm/elm.json new file mode 100644 index 000000000..a95f80408 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/elm.json @@ -0,0 +1,30 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "elm/browser": "1.0.2", + "elm/core": "1.0.5", + "elm/html": "1.0.0", + "elm/random": "1.0.0", + "elm/svg": "1.0.1", + "elm/time": "1.0.0", + "elm-community/list-extra": "8.2.3", + "elm-community/maybe-extra": "5.2.0", + "elm-community/random-extra": "3.1.0" + }, + "indirect": { + "elm/json": "1.1.3", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2", + "owanturist/elm-union-find": "1.0.0" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/users/wpcarro/boilerplate/elm/index.css b/users/wpcarro/boilerplate/elm/index.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/users/wpcarro/boilerplate/elm/index.html b/users/wpcarro/boilerplate/elm/index.html new file mode 100644 index 000000000..ce8f727b6 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/index.html @@ -0,0 +1,15 @@ + + + + + Elm SPA + + + + +
+ + + diff --git a/users/wpcarro/boilerplate/elm/shell.nix b/users/wpcarro/boilerplate/elm/shell.nix new file mode 100644 index 000000000..00bb4b0b3 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/shell.nix @@ -0,0 +1,10 @@ +let + briefcase = import {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs.elmPackages; [ + elm + elm-format + elm-live + ]; +} diff --git a/users/wpcarro/boilerplate/elm/src/Landing.elm b/users/wpcarro/boilerplate/elm/src/Landing.elm new file mode 100644 index 000000000..00bb9e281 --- /dev/null +++ b/users/wpcarro/boilerplate/elm/src/Landing.elm @@ -0,0 +1,13 @@ +module Landing exposing (render) + +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import State + + +render : State.Model -> Html State.Msg +render model = + div [ class "pt-10 pb-20 px-10" ] + [ p [] [ text "Welcome to the landing page!" ] + ] diff --git a/users/wpcarro/boilerplate/elm/src/Login.elm b/users/wpcarro/boilerplate/elm/src/Login.elm new file mode 100644 index 000000000..27f1d811a --- /dev/null +++ b/users/wpcarro/boilerplate/elm/src/Login.elm @@ -0,0 +1,13 @@ +module Login exposing (render) + +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import State + + +render : State.Model -> Html State.Msg +render model = + div [ class "pt-10 pb-20 px-10" ] + [ p [] [ text "Please authenticate" ] + ] diff --git a/users/wpcarro/boilerplate/elm/src/Main.elm b/users/wpcarro/boilerplate/elm/src/Main.elm new file mode 100644 index 000000000..30006460c --- /dev/null +++ b/users/wpcarro/boilerplate/elm/src/Main.elm @@ -0,0 +1,31 @@ +module Main exposing (main) + +import Browser +import Html exposing (..) +import Landing +import Login +import State + + +subscriptions : State.Model -> Sub State.Msg +subscriptions model = + Sub.none + + +view : State.Model -> Html State.Msg +view model = + case model.view of + State.Landing -> + Landing.render model + + State.Login -> + Login.render model + + +main = + Browser.element + { init = \() -> ( State.init, Cmd.none ) + , subscriptions = subscriptions + , update = State.update + , view = view + } diff --git a/users/wpcarro/boilerplate/elm/src/State.elm b/users/wpcarro/boilerplate/elm/src/State.elm new file mode 100644 index 000000000..c1edae8bb --- /dev/null +++ b/users/wpcarro/boilerplate/elm/src/State.elm @@ -0,0 +1,43 @@ +module State exposing (..) + + +type Msg + = DoNothing + | SetView View + + +type View + = Landing + | Login + + +type alias Model = + { isLoading : Bool + , view : View + } + + +{-| The initial state for the application. +-} +init : Model +init = + { isLoading = False + , view = Landing + } + + +{-| Now that we have state, we need a function to change the state. +-} +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + DoNothing -> + ( model, Cmd.none ) + + SetView x -> + ( { model + | view = x + , isLoading = True + } + , Cmd.none + ) diff --git a/users/wpcarro/boilerplate/typescript/.envrc b/users/wpcarro/boilerplate/typescript/.envrc new file mode 100644 index 000000000..a4a62da52 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/boilerplate/typescript/.gitignore b/users/wpcarro/boilerplate/typescript/.gitignore new file mode 100644 index 000000000..ebea22e07 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/.gitignore @@ -0,0 +1,3 @@ +/.cache +/dist +/node_modules \ No newline at end of file diff --git a/users/wpcarro/boilerplate/typescript/README.md b/users/wpcarro/boilerplate/typescript/README.md new file mode 100644 index 000000000..a54186a9f --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/README.md @@ -0,0 +1,26 @@ +# Frontend Boilerplate + +While many times I prefer using alt-languages like ReasonML, ClojureScript, or +Elm, sometimes I prefer to write an application using TypeScript. This directory +contains the necessary starter code to create these applications. + +- React: Maps application state to UI +- React-Router: Stateful routing for SPAs +- Redux: Application state management +- TypeScript: Type-safety +- TailwindCSS: Styling library using utility classes +- Prettier: Source code formatting +- Jest: Test runner + +## Developing + +```shell +$ nix-shell +$ yarn run dev +``` + +## Building + +```shell +$ nix-build +``` diff --git a/users/wpcarro/boilerplate/typescript/default.nix b/users/wpcarro/boilerplate/typescript/default.nix new file mode 100644 index 000000000..14a39dee1 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/default.nix @@ -0,0 +1,19 @@ +{ pkgs, ... }: + +pkgs.stdenv.mkDerivation { + name = "typescript"; + srcs = builtins.path { path = ./.; name = "typescript"; }; + buildInputs = with pkgs; [ + nodejs + # Exposes lscpu for parcel.js + utillinux + ]; + # parcel.js needs number of CPUs + PARCEL_WORKERS = "1"; + buildPhase = '' + npx parcel build src/index.html --public-url ./ + ''; + installPhase = '' + mv dist $out + ''; +} diff --git a/users/wpcarro/boilerplate/typescript/package.json b/users/wpcarro/boilerplate/typescript/package.json new file mode 100644 index 000000000..104e7272d --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/package.json @@ -0,0 +1,27 @@ +{ + "name": "tailwindcss", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "dev": "parcel src/index.html & npx tsc --watch --noEmit", + "prettier": "prettier --ignore-path .gitignore --write \"**/*.{js,ts,jsx,tsx,html,css.json}\"" + }, + "devDependencies": { + "@types/node": "^13.9.3", + "parcel-bundler": "^1.12.4", + "prettier": "^2.0.2", + "tailwindcss": "^1.2.0", + "typescript": "^3.8.3" + }, + "dependencies": { + "@reduxjs/toolkit": "^1.2.5", + "@types/react-dom": "^16.9.5", + "@types/react-redux": "^7.1.7", + "@types/react-router-dom": "^5.1.3", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-redux": "^7.2.0", + "react-router-dom": "^5.1.2" + } +} diff --git a/users/wpcarro/boilerplate/typescript/postcss.config.js b/users/wpcarro/boilerplate/typescript/postcss.config.js new file mode 100644 index 000000000..d68fa6186 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/postcss.config.js @@ -0,0 +1,7 @@ +const tailwindcss = require('tailwindcss') + +module.exports = { + plugins: [ + tailwindcss('./tailwind.config.js') + ] +} diff --git a/users/wpcarro/boilerplate/typescript/shell.nix b/users/wpcarro/boilerplate/typescript/shell.nix new file mode 100644 index 000000000..083254bee --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/shell.nix @@ -0,0 +1,9 @@ +let + briefcase = import {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs; [ + nodejs + yarn + ]; +} diff --git a/users/wpcarro/boilerplate/typescript/src/App.tsx b/users/wpcarro/boilerplate/typescript/src/App.tsx new file mode 100644 index 000000000..4fae1b36a --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/src/App.tsx @@ -0,0 +1,52 @@ +import React, { useEffect } from "react"; +import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; +import { useDispatch } from "react-redux"; +import { actions, useTypedSelector } from "./store"; +import { Link } from "react-router-dom"; + +const App: React.FC = () => { + const dispatch = useDispatch(); + const { isLoading } = useTypedSelector(state => ({ + isLoading: state.isLoading, + })); + + return ( + + + + +
+

Welcome to the home page. Loading: {isLoading ? "true" : "false"}

+ +
+
+ +
+

Here is the about page.

+
+
+ +
+

Here is the contact page.

+
+
+
+
+ ); +}; + +export default App; diff --git a/users/wpcarro/boilerplate/typescript/src/index.css b/users/wpcarro/boilerplate/typescript/src/index.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/users/wpcarro/boilerplate/typescript/src/index.html b/users/wpcarro/boilerplate/typescript/src/index.html new file mode 100644 index 000000000..91752af91 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/src/index.html @@ -0,0 +1,11 @@ + + + + + + + +
+ + + diff --git a/users/wpcarro/boilerplate/typescript/src/index.tsx b/users/wpcarro/boilerplate/typescript/src/index.tsx new file mode 100644 index 000000000..dc28dc4a9 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/src/index.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import App from "./App"; +import { Provider } from "react-redux"; +import store from "./store"; + +ReactDOM.render( + + + , + document.getElementById("mount") +); diff --git a/users/wpcarro/boilerplate/typescript/src/store.ts b/users/wpcarro/boilerplate/typescript/src/store.ts new file mode 100644 index 000000000..03e980a49 --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/src/store.ts @@ -0,0 +1,26 @@ +import { createSlice, configureStore, PayloadAction } from "@reduxjs/toolkit"; +import { useSelector, TypedUseSelectorHook } from "react-redux"; + +export interface State { + isLoading: boolean; +} + +const initialState: State = { + isLoading: true, +}; + +export const { actions, reducer } = createSlice({ + name: "application", + initialState, + reducers: { + toggleIsLoading: state => ({ ...state, isLoading: !state.isLoading }), + } +}); + +/** + * Defining and consuming this allows us to avoid annotating State in all of our + * selectors. + */ +export const useTypedSelector: TypedUseSelectorHook = useSelector; + +export default configureStore({ reducer }); diff --git a/users/wpcarro/boilerplate/typescript/tailwind.config.js b/users/wpcarro/boilerplate/typescript/tailwind.config.js new file mode 100644 index 000000000..af829e20f --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/tailwind.config.js @@ -0,0 +1,7 @@ +module.exports = { + theme: { + extend: {}, + }, + variants: {}, + plugins: [], +} diff --git a/users/wpcarro/boilerplate/typescript/tsconfig.json b/users/wpcarro/boilerplate/typescript/tsconfig.json new file mode 100644 index 000000000..013f34fdf --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": [ + "src/**/*" + ] +} diff --git a/users/wpcarro/boilerplate/typescript/yarn.lock b/users/wpcarro/boilerplate/typescript/yarn.lock new file mode 100644 index 000000000..0e16fe80a --- /dev/null +++ b/users/wpcarro/boilerplate/typescript/yarn.lock @@ -0,0 +1,5670 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== + dependencies: + "@babel/highlight" "^7.8.3" + +"@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" + integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g== + dependencies: + browserslist "^4.9.1" + invariant "^2.2.4" + semver "^5.5.0" + +"@babel/core@^7.4.4": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" + integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.0" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helpers" "^7.9.0" + "@babel/parser" "^7.9.0" + "@babel/template" "^7.8.6" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.4.4", "@babel/generator@^7.9.0": + version "7.9.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.3.tgz#7c8b2956c6f68b3ab732bd16305916fbba521d94" + integrity sha512-RpxM252EYsz9qLUIq6F7YJyK1sv0wWDBFuztfDGWaQKzHjqDHysxSiRUpA/X9jmfqo+WzkAVKFaUily5h+gDCQ== + dependencies: + "@babel/types" "^7.9.0" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" + integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" + integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-builder-react-jsx-experimental@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.0.tgz#066d80262ade488f9c1b1823ce5db88a4cedaa43" + integrity sha512-3xJEiyuYU4Q/Ar9BsHisgdxZsRlsShMe90URZ0e6przL26CCs8NJbDoxH94kKT17PcxlMhsCAwZd90evCo26VQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-module-imports" "^7.8.3" + "@babel/types" "^7.9.0" + +"@babel/helper-builder-react-jsx@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz#16bf391990b57732700a3278d4d9a81231ea8d32" + integrity sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/types" "^7.9.0" + +"@babel/helper-compilation-targets@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" + integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw== + dependencies: + "@babel/compat-data" "^7.8.6" + browserslist "^4.9.1" + invariant "^2.2.4" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" + integrity sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-regex" "^7.8.3" + regexpu-core "^4.7.0" + +"@babel/helper-define-map@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" + integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/types" "^7.8.3" + lodash "^4.17.13" + +"@babel/helper-explode-assignable-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" + integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw== + dependencies: + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" + integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-get-function-arity@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" + integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-hoist-variables@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" + integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-member-expression-to-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" + integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-imports@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" + integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-transforms@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" + integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-simple-access" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/template" "^7.8.6" + "@babel/types" "^7.9.0" + lodash "^4.17.13" + +"@babel/helper-optimise-call-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" + integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== + +"@babel/helper-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" + integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ== + dependencies: + lodash "^4.17.13" + +"@babel/helper-remap-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" + integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-wrap-function" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" + integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/traverse" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/helper-simple-access@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" + integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== + dependencies: + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-split-export-declaration@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" + integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-validator-identifier@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed" + integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw== + +"@babel/helper-wrap-function@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" + integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helpers@^7.9.0": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" + integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== + dependencies: + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + +"@babel/highlight@^7.8.3": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" + integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== + dependencies: + "@babel/helper-validator-identifier" "^7.9.0" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.4.4", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0": + version "7.9.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.3.tgz#043a5fc2ad8b7ea9facddc4e802a1f0f25da7255" + integrity sha512-E6SpIDJZ0cZAKoCNk+qSDd0ChfTnpiJN9FfNf3RZ20dzwA2vL2oq5IX1XTVT+4vDmRlta2nGk5HGMMskJAR+4A== + +"@babel/plugin-proposal-async-generator-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" + integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + +"@babel/plugin-proposal-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" + integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + +"@babel/plugin-proposal-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" + integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.0" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" + integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + +"@babel/plugin-proposal-numeric-separator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" + integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + +"@babel/plugin-proposal-object-rest-spread@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz#a28993699fc13df165995362693962ba6b061d6f" + integrity sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + +"@babel/plugin-proposal-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" + integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" + integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" + integrity sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.8" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-async-generators@^7.8.0": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-dynamic-import@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-flow@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.8.3.tgz#f2c883bd61a6316f2c89380ae5122f923ba4527f" + integrity sha512-innAx3bUbA0KSYj2E2MNFSn9hiCeowOFLxlsuhXzw8hMQnzkDomUr9QCD7E9VF60NmnG1sNTuuv6Qf4f8INYsg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-json-strings@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz#521b06c83c40480f1e58b4fd33b92eceb1d6ea94" + integrity sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f" + integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-object-rest-spread@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" + integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-arrow-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" + integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" + integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + +"@babel/plugin-transform-block-scoped-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" + integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-block-scoping@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" + integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + lodash "^4.17.13" + +"@babel/plugin-transform-classes@^7.9.0": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz#8603fc3cc449e31fdbdbc257f67717536a11af8d" + integrity sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-define-map" "^7.8.3" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-split-export-declaration" "^7.8.3" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" + integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-destructuring@^7.8.3": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b" + integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" + integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-duplicate-keys@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" + integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" + integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-flow-strip-types@^7.4.4": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.9.0.tgz#8a3538aa40434e000b8f44a3c5c9ac7229bd2392" + integrity sha512-7Qfg0lKQhEHs93FChxVLAvhBshOPQDtJUTVHr/ZwQNRccCm4O9D79r9tVSoV8iNwjP1YgfD+e/fgHcPkN1qEQg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-flow" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" + integrity sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" + integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" + integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-member-expression-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" + integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-modules-amd@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" + integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-commonjs@^7.4.4", "@babel/plugin-transform-modules-commonjs@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" + integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-simple-access" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-systemjs@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" + integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ== + dependencies: + "@babel/helper-hoist-variables" "^7.8.3" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + babel-plugin-dynamic-import-node "^2.3.0" + +"@babel/plugin-transform-modules-umd@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" + integrity sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ== + dependencies: + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" + integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + +"@babel/plugin-transform-new-target@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" + integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-object-super@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" + integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.8.7": + version "7.9.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz#3028d0cc20ddc733166c6e9c8534559cee09f54a" + integrity sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-property-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" + integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-react-jsx@^7.0.0": + version "7.9.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.1.tgz#d03af29396a6dc51bfa24eefd8005a9fd381152a" + integrity sha512-+xIZ6fPoix7h57CNO/ZeYADchg1tFyX9NDsnmNFFua8e1JNPln156mzS+8AQe1On2X2GLlANHJWHIXbMCqWDkQ== + dependencies: + "@babel/helper-builder-react-jsx" "^7.9.0" + "@babel/helper-builder-react-jsx-experimental" "^7.9.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-jsx" "^7.8.3" + +"@babel/plugin-transform-regenerator@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8" + integrity sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-reserved-words@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" + integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-shorthand-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" + integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" + integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-sticky-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" + integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-regex" "^7.8.3" + +"@babel/plugin-transform-template-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" + integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-typeof-symbol@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" + integrity sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-unicode-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" + integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/preset-env@^7.4.4": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.0.tgz#a5fc42480e950ae8f5d9f8f2bbc03f52722df3a8" + integrity sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ== + dependencies: + "@babel/compat-data" "^7.9.0" + "@babel/helper-compilation-targets" "^7.8.7" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-proposal-async-generator-functions" "^7.8.3" + "@babel/plugin-proposal-dynamic-import" "^7.8.3" + "@babel/plugin-proposal-json-strings" "^7.8.3" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-proposal-numeric-separator" "^7.8.3" + "@babel/plugin-proposal-object-rest-spread" "^7.9.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" + "@babel/plugin-proposal-optional-chaining" "^7.9.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.8.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.8.3" + "@babel/plugin-transform-async-to-generator" "^7.8.3" + "@babel/plugin-transform-block-scoped-functions" "^7.8.3" + "@babel/plugin-transform-block-scoping" "^7.8.3" + "@babel/plugin-transform-classes" "^7.9.0" + "@babel/plugin-transform-computed-properties" "^7.8.3" + "@babel/plugin-transform-destructuring" "^7.8.3" + "@babel/plugin-transform-dotall-regex" "^7.8.3" + "@babel/plugin-transform-duplicate-keys" "^7.8.3" + "@babel/plugin-transform-exponentiation-operator" "^7.8.3" + "@babel/plugin-transform-for-of" "^7.9.0" + "@babel/plugin-transform-function-name" "^7.8.3" + "@babel/plugin-transform-literals" "^7.8.3" + "@babel/plugin-transform-member-expression-literals" "^7.8.3" + "@babel/plugin-transform-modules-amd" "^7.9.0" + "@babel/plugin-transform-modules-commonjs" "^7.9.0" + "@babel/plugin-transform-modules-systemjs" "^7.9.0" + "@babel/plugin-transform-modules-umd" "^7.9.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" + "@babel/plugin-transform-new-target" "^7.8.3" + "@babel/plugin-transform-object-super" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.8.7" + "@babel/plugin-transform-property-literals" "^7.8.3" + "@babel/plugin-transform-regenerator" "^7.8.7" + "@babel/plugin-transform-reserved-words" "^7.8.3" + "@babel/plugin-transform-shorthand-properties" "^7.8.3" + "@babel/plugin-transform-spread" "^7.8.3" + "@babel/plugin-transform-sticky-regex" "^7.8.3" + "@babel/plugin-transform-template-literals" "^7.8.3" + "@babel/plugin-transform-typeof-symbol" "^7.8.4" + "@babel/plugin-transform-unicode-regex" "^7.8.3" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.9.0" + browserslist "^4.9.1" + core-js-compat "^3.6.2" + invariant "^2.2.2" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/preset-modules@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" + integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" + integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.4.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" + integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/traverse@^7.4.4", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.0.tgz#d3882c2830e513f4fe4cec9fe76ea1cc78747892" + integrity sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.0" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.9.0" + "@babel/types" "^7.9.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + +"@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.0.tgz#00b064c3df83ad32b2dbf5ff07312b15c7f1efb5" + integrity sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng== + dependencies: + "@babel/helper-validator-identifier" "^7.9.0" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + +"@iarna/toml@^2.2.0": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.3.tgz#f060bf6eaafae4d56a7dac618980838b0696e2ab" + integrity sha512-FmuxfCuolpLl0AnQ2NHSzoUKWEJDFl63qXjzdoWBVyFCXzMGm1spBzk7LeHNoVCiWCF7mRVms9e6jEV9+MoPbg== + +"@mrmlnc/readdir-enhanced@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" + integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== + dependencies: + call-me-maybe "^1.0.1" + glob-to-regexp "^0.3.0" + +"@nodelib/fs.stat@^1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" + integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== + +"@parcel/fs@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-1.11.0.tgz#fb8a2be038c454ad46a50dc0554c1805f13535cd" + integrity sha512-86RyEqULbbVoeo8OLcv+LQ1Vq2PKBAvWTU9fCgALxuCTbbs5Ppcvll4Vr+Ko1AnmMzja/k++SzNAwJfeQXVlpA== + dependencies: + "@parcel/utils" "^1.11.0" + mkdirp "^0.5.1" + rimraf "^2.6.2" + +"@parcel/logger@^1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-1.11.1.tgz#c55b0744bcbe84ebc291155627f0ec406a23e2e6" + integrity sha512-9NF3M6UVeP2udOBDILuoEHd8VrF4vQqoWHEafymO1pfSoOMfxrSJZw1MfyAAIUN/IFp9qjcpDCUbDZB+ioVevA== + dependencies: + "@parcel/workers" "^1.11.0" + chalk "^2.1.0" + grapheme-breaker "^0.3.2" + ora "^2.1.0" + strip-ansi "^4.0.0" + +"@parcel/utils@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-1.11.0.tgz#539e08fff8af3b26eca11302be80b522674b51ea" + integrity sha512-cA3p4jTlaMeOtAKR/6AadanOPvKeg8VwgnHhOyfi0yClD0TZS/hi9xu12w4EzA/8NtHu0g6o4RDfcNjqN8l1AQ== + +"@parcel/watcher@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-1.12.1.tgz#b98b3df309fcab93451b5583fc38e40826696dad" + integrity sha512-od+uCtCxC/KoNQAIE1vWx1YTyKYY+7CTrxBJPRh3cDWw/C0tCtlBMVlrbplscGoEpt6B27KhJDCv82PBxOERNA== + dependencies: + "@parcel/utils" "^1.11.0" + chokidar "^2.1.5" + +"@parcel/workers@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-1.11.0.tgz#7b8dcf992806f4ad2b6cecf629839c41c2336c59" + integrity sha512-USSjRAAQYsZFlv43FUPdD+jEGML5/8oLF0rUzPQTtK4q9kvaXr49F5ZplyLz5lox78cLZ0TxN2bIDQ1xhOkulQ== + dependencies: + "@parcel/utils" "^1.11.0" + physical-cpu-count "^2.0.0" + +"@reduxjs/toolkit@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.2.5.tgz#149aa62da12a18a67a30495cb63fd897003f2272" + integrity sha512-/OWoW5mniUXAomw4+3ZhhWodcs1/SRvK2HKyxLXdW6vKgmJhiBiSHe/huHARlKWujEmGaJrkafx548GE494bCQ== + dependencies: + immer "^4.0.1" + redux "^4.0.0" + redux-devtools-extension "^2.13.8" + redux-immutable-state-invariant "^2.1.0" + redux-thunk "^2.3.0" + reselect "^4.0.0" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@types/history@*": + version "4.7.5" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" + integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw== + +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/node@^13.9.3": + version "13.9.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.3.tgz#6356df2647de9eac569f9a52eda3480fa9e70b4d" + integrity sha512-01s+ac4qerwd6RHD+mVbOEsraDHSgUaefQlEdBbUolnQFjKwCr7luvAlEwW1RFojh67u0z4OUTjPn9LEl4zIkA== + +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + +"@types/q@^1.5.1": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" + integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== + +"@types/react-dom@^16.9.5": + version "16.9.5" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.5.tgz#5de610b04a35d07ffd8f44edad93a71032d9aaa7" + integrity sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg== + dependencies: + "@types/react" "*" + +"@types/react-redux@^7.1.7": + version "7.1.7" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.7.tgz#12a0c529aba660696947384a059c5c6e08185c7a" + integrity sha512-U+WrzeFfI83+evZE2dkZ/oF/1vjIYgqrb5dGgedkqVV8HEfDFujNgWCwHL89TDuWKb47U0nTBT6PLGq4IIogWg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + +"@types/react-router-dom@^5.1.3": + version "5.1.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196" + integrity sha512-pCq7AkOvjE65jkGS5fQwQhvUp4+4PVD9g39gXLZViP2UqFiFzsEpB3PKf0O6mdbKsewSK8N14/eegisa/0CwnA== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.4.tgz#7d70bd905543cb6bcbdcc6bd98902332054f31a6" + integrity sha512-PZtnBuyfL07sqCJvGg3z+0+kt6fobc/xmle08jBiezLS8FrmGeiGkJnuxL/8Zgy9L83ypUhniV5atZn/L8n9MQ== + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react@*": + version "16.9.25" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.25.tgz#6ae2159b40138c792058a23c3c04fd3db49e929e" + integrity sha512-Dlj2V72cfYLPNscIG3/SMUOzhzj7GK3bpSrfefwt2YT9GLynvLCCZjbhyF6VsT0q0+aRACRX03TDJGb7cA0cqg== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + +abab@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" + integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== + +acorn-globals@^4.3.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" + integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== + dependencies: + acorn "^6.0.1" + acorn-walk "^6.0.1" + +acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" + integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== + +acorn-walk@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e" + integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ== + +acorn@^6.0.1, acorn@^6.0.4: + version "6.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" + integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== + +acorn@^7.0.0, acorn@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" + integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== + +ajv@^6.5.5: + version "6.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" + integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +alphanum-sort@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +ansi-to-html@^0.6.4: + version "0.6.14" + resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.14.tgz#65fe6d08bba5dd9db33f44a20aec331e0010dad8" + integrity sha512-7ZslfB1+EnFSDO5Ju+ue5Y6It19DRnZXWv8jrGHgIlPna5Mh4jz7BV5jCbQneXNFurQcKoolaaAjHtgSBfOIuA== + dependencies: + entities "^1.1.2" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +asn1.js@^4.0.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assert@^1.1.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + dependencies: + object-assign "^4.1.1" + util "0.10.3" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +autoprefixer@^9.4.5: + version "9.7.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.4.tgz#f8bf3e06707d047f0641d87aee8cfb174b2a5378" + integrity sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g== + dependencies: + browserslist "^4.8.3" + caniuse-lite "^1.0.30001020" + chalk "^2.4.2" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.26" + postcss-value-parser "^4.0.2" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + +babel-plugin-dynamic-import-node@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" + integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== + dependencies: + object.assign "^4.1.0" + +babel-runtime@^6.11.6, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-types@^6.15.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon-walk@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/babylon-walk/-/babylon-walk-1.0.2.tgz#3b15a5ddbb482a78b4ce9c01c8ba181702d9d6ce" + integrity sha1-OxWl3btIKni0zpwByLoYFwLZ1s4= + dependencies: + babel-runtime "^6.11.6" + babel-types "^6.15.0" + lodash.clone "^4.5.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.0.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +brfs@^1.2.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.6.1.tgz#b78ce2336d818e25eea04a0947cba6d4fb8849c3" + integrity sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ== + dependencies: + quote-stream "^1.0.1" + resolve "^1.1.5" + static-module "^2.2.0" + through2 "^2.0.0" + +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= + dependencies: + bn.js "^4.1.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= + dependencies: + bn.js "^4.1.1" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.2" + elliptic "^6.0.0" + inherits "^2.0.1" + parse-asn1 "^5.0.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + +browserslist@^4.0.0, browserslist@^4.1.0, browserslist@^4.8.3, browserslist@^4.9.1: + version "4.11.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.0.tgz#aef4357b10a8abda00f97aac7cd587b2082ba1ad" + integrity sha512-WqEC7Yr5wUH5sg6ruR++v2SGOQYpyUdYYd4tZoAq1F7y+QXoLoYGXVbxhtaIqWmAJjtNTRjVD3HuJc1OXTel2A== + dependencies: + caniuse-lite "^1.0.30001035" + electron-to-chromium "^1.3.380" + node-releases "^1.1.52" + pkg-up "^3.1.0" + +buffer-equal@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" + integrity sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs= + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + +buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= + +bytes@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +call-me-maybe@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" + integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001035: + version "1.0.30001036" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001036.tgz#930ea5272010d8bf190d859159d757c0b398caf0" + integrity sha512-jU8CIFIj2oR7r4W+5AKcsvWNVIb6Q6OZE3UsrXrZBHFtreT4YgTeOJtTucp+zSedEpTi3L5wASSP0LYIE3if6w== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^2.1.5: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@^1.1.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a" + integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg== + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + +coa@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== + dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" + q "^1.1.2" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0, color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" + integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" + integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +command-exists@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291" + integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw== + +commander@^2.11.0, commander@^2.19.0, commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@~1.6.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +console-browserify@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= + +convert-source-map@^1.5.1, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-js-compat@^3.6.2: + version "3.6.4" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" + integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA== + dependencies: + browserslist "^4.8.3" + semver "7.0.0" + +core-js@^2.4.0, core-js@^2.6.5: + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cosmiconfig@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + +create-ecdh@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" + integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== + dependencies: + bn.js "^4.1.0" + elliptic "^6.0.0" + +create-hash@^1.1.0, create-hash@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@^6.0.4: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +css-color-names@0.0.4, css-color-names@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= + +css-declaration-sorter@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" + integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== + dependencies: + postcss "^7.0.1" + timsort "^0.3.0" + +css-modules-loader-core@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz#5908668294a1becd261ae0a4ce21b0b551f21d16" + integrity sha1-WQhmgpShvs0mGuCkziGwtVHyHRY= + dependencies: + icss-replace-symbols "1.1.0" + postcss "6.0.1" + postcss-modules-extract-imports "1.1.0" + postcss-modules-local-by-default "1.2.0" + postcss-modules-scope "1.1.0" + postcss-modules-values "1.3.0" + +css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== + +css-select@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== + dependencies: + boolbase "^1.0.0" + css-what "^3.2.1" + domutils "^1.7.0" + nth-check "^1.0.2" + +css-selector-tokenizer@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz#11e5e27c9a48d90284f22d45061c303d7a25ad87" + integrity sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw== + dependencies: + cssesc "^3.0.0" + fastparse "^1.1.2" + regexpu-core "^4.6.0" + +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + +css-unit-converter@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996" + integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY= + +css-what@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" + integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" + integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== + dependencies: + css-declaration-sorter "^4.0.1" + cssnano-util-raw-cache "^4.0.1" + postcss "^7.0.0" + postcss-calc "^7.0.1" + postcss-colormin "^4.0.3" + postcss-convert-values "^4.0.1" + postcss-discard-comments "^4.0.2" + postcss-discard-duplicates "^4.0.2" + postcss-discard-empty "^4.0.1" + postcss-discard-overridden "^4.0.1" + postcss-merge-longhand "^4.0.11" + postcss-merge-rules "^4.0.3" + postcss-minify-font-values "^4.0.2" + postcss-minify-gradients "^4.0.2" + postcss-minify-params "^4.0.2" + postcss-minify-selectors "^4.0.2" + postcss-normalize-charset "^4.0.1" + postcss-normalize-display-values "^4.0.2" + postcss-normalize-positions "^4.0.2" + postcss-normalize-repeat-style "^4.0.2" + postcss-normalize-string "^4.0.2" + postcss-normalize-timing-functions "^4.0.2" + postcss-normalize-unicode "^4.0.1" + postcss-normalize-url "^4.0.1" + postcss-normalize-whitespace "^4.0.2" + postcss-ordered-values "^4.1.2" + postcss-reduce-initial "^4.0.3" + postcss-reduce-transforms "^4.0.2" + postcss-svgo "^4.0.2" + postcss-unique-selectors "^4.0.1" + +cssnano-util-get-arguments@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" + integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= + +cssnano-util-get-match@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" + integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= + +cssnano-util-raw-cache@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" + integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== + dependencies: + postcss "^7.0.0" + +cssnano-util-same-parent@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" + integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== + +cssnano@^4.0.0, cssnano@^4.1.10: + version "4.1.10" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" + integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== + dependencies: + cosmiconfig "^5.0.0" + cssnano-preset-default "^4.0.7" + is-resolvable "^1.0.0" + postcss "^7.0.0" + +csso@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.2.tgz#e5f81ab3a56b8eefb7f0092ce7279329f454de3d" + integrity sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg== + dependencies: + css-tree "1.0.0-alpha.37" + +cssom@0.3.x, cssom@^0.3.4: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^1.1.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" + integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== + dependencies: + cssom "0.3.x" + +csstype@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" + integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +data-urls@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" + integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.2.0" + whatwg-url "^7.0.0" + +deasync@^0.1.14: + version "0.1.19" + resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.19.tgz#e7ea89fcc9ad483367e8a48fe78f508ca86286e8" + integrity sha512-oh3MRktfnPlLysCPpBpKZZzb4cUC/p0aA3SyRGp15lN30juJBTo/CiD0d4fR+f1kBtUQoJj1NE9RPNWQ7BQ9Mg== + dependencies: + bindings "^1.5.0" + node-addon-api "^1.7.1" + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + +domelementtype@1, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" + integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== + +domexception@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== + dependencies: + webidl-conversions "^4.0.2" + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + +domutils@^1.5.1, domutils@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +dot-prop@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" + integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== + dependencies: + is-obj "^2.0.0" + +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" + integrity sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow== + +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +electron-to-chromium@^1.3.380: + version "1.3.381" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.381.tgz#952678ff91a5f36175a3832358a6dd2de3bf62b7" + integrity sha512-JQBpVUr83l+QOqPQpj2SbOve1bBE4ACpmwcMNqWlZmfib7jccxJ02qFNichDpZ5LS4Zsqc985NIPKegBIZjK8Q== + +elliptic@^6.0.0: + version "6.5.2" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" + integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +entities@^1.1.1, entities@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" + integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== + +envinfo@^7.3.1: + version "7.5.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.0.tgz#91410bb6db262fb4f1409bd506e9ff57e91023f4" + integrity sha512-jDgnJaF/Btomk+m3PZDTTCb5XIIIX3zYItnCRfF73zVgvinLoRomuhi75Y4su0PtQxWz4v66XnLLckyvyJTOIQ== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: + version "1.17.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" + integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escodegen@^1.11.0, escodegen@^1.11.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457" + integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +escodegen@~1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" + integrity sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q== + dependencies: + esprima "^3.1.3" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +events@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" + integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +falafel@^2.1.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.2.4.tgz#b5d86c060c2412a43166243cb1bce44d1abd2819" + integrity sha512-0HXjo8XASWRmsS0X1EkhwEMZaD3Qvp7FfURwjLKjG1ghfRm/MGZl2r4cWUTv41KdNghTw4OUMmVtdGQp3+H+uQ== + dependencies: + acorn "^7.1.1" + foreach "^2.0.5" + isarray "^2.0.1" + object-keys "^1.0.6" + +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + +fast-glob@^2.2.2: + version "2.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" + integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== + dependencies: + "@mrmlnc/readdir-enhanced" "^2.2.1" + "@nodelib/fs.stat" "^1.1.2" + glob-parent "^3.1.0" + is-glob "^4.0.0" + merge2 "^1.2.3" + micromatch "^3.1.10" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastparse@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" + integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +filesize@^3.6.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" + integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs-extra@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.12" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.12.tgz#db7e0d8ec3b0b45724fd4d83d43554a8f1f0de5c" + integrity sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-port@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" + integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-to-regexp@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" + integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= + +glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +grapheme-breaker@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/grapheme-breaker/-/grapheme-breaker-0.3.2.tgz#5b9e6b78c3832452d2ba2bb1cb830f96276410ac" + integrity sha1-W55reMODJFLSuiuxy4MPlidkEKw= + dependencies: + brfs "^1.2.0" + unicode-trie "^0.3.1" + +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.0, has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hex-color-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== + +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hsl-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" + integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= + +hsla-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" + integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= + +html-comment-regex@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" + integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== + +html-encoding-sniffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== + dependencies: + whatwg-encoding "^1.0.1" + +html-tags@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-1.2.0.tgz#c78de65b5663aa597989dd2b7ab49200d7e4db98" + integrity sha1-x43mW1Zjqll5id0rerSSANfk25g= + +htmlnano@^0.2.2: + version "0.2.5" + resolved "https://registry.yarnpkg.com/htmlnano/-/htmlnano-0.2.5.tgz#134fd9548c7cbe51c8508ce434a3f9488cff1b0b" + integrity sha512-X1iPSwXG/iF9bVs+/obt2n6F64uH0ETkA8zp7qFDmLW9/+A6ueHGeb/+qD67T21qUY22owZPMdawljN50ajkqA== + dependencies: + cssnano "^4.1.10" + normalize-html-whitespace "^1.0.0" + posthtml "^0.12.0" + posthtml-render "^1.1.5" + purgecss "^1.4.0" + svgo "^1.3.2" + terser "^4.3.9" + uncss "^0.17.2" + +htmlparser2@^3.9.2: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-replace-symbols@1.1.0, icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= + +ieee754@^1.1.4: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +immer@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/immer/-/immer-4.0.2.tgz#9ff0fcdf88e06f92618a5978ceecb5884e633559" + integrity sha512-Q/tm+yKqnKy4RIBmmtISBlhXuSDrB69e9EKTYiIenIKQkXBQir43w+kN/eGiax3wt1J0O1b2fYcNqLSbEcXA7w== + +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +invariant@^2.1.0, invariant@^2.2.2, invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= + +is-absolute-url@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.4, is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== + +is-color-stop@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" + integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= + dependencies: + css-color-names "^0.0.4" + hex-color-regex "^1.1.0" + hsl-regex "^1.0.0" + hsla-regex "^1.0.0" + rgb-regex "^1.0.1" + rgba-regex "^1.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-html@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-html/-/is-html-1.1.0.tgz#e04f1c18d39485111396f9a0273eab51af218464" + integrity sha1-4E8cGNOUhRETlvmgJz6rUa8hhGQ= + dependencies: + html-tags "^1.0.0" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== + dependencies: + has "^1.0.3" + +is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== + +is-svg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" + integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== + dependencies: + html-comment-regex "^1.1.0" + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-url@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isarray@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.10.0, js-yaml@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdom@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-14.1.0.tgz#916463b6094956b0a6c1782c94e380cd30e1981b" + integrity sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng== + dependencies: + abab "^2.0.0" + acorn "^6.0.4" + acorn-globals "^4.3.0" + array-equal "^1.0.0" + cssom "^0.3.4" + cssstyle "^1.1.1" + data-urls "^1.1.0" + domexception "^1.0.1" + escodegen "^1.11.0" + html-encoding-sniffer "^1.0.2" + nwsapi "^2.1.3" + parse5 "5.1.0" + pn "^1.1.0" + request "^2.88.0" + request-promise-native "^1.0.5" + saxes "^3.1.9" + symbol-tree "^3.2.2" + tough-cookie "^2.5.0" + w3c-hr-time "^1.0.1" + w3c-xmlserializer "^1.1.2" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^7.0.0" + ws "^6.1.2" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e" + integrity sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ== + dependencies: + minimist "^1.2.5" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levenary@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" + integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== + dependencies: + leven "^3.1.0" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash.clone@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash.toarray@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" + integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.4: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +log-symbols@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + dependencies: + chalk "^2.0.1" + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +magic-string@^0.22.4: + version "0.22.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e" + integrity sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w== + dependencies: + vlq "^0.2.2" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + +merge-source-map@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.0.4.tgz#a5de46538dae84d4114cc5ea02b4772a6346701f" + integrity sha1-pd5GU42uhNQRTMXqArR3KmNGcB8= + dependencies: + source-map "^0.5.6" + +merge2@^1.2.3: + version "1.3.0" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" + integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== + +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +mini-create-react-context@^0.3.0: + version "0.3.2" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" + integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== + dependencies: + "@babel/runtime" "^7.4.0" + gud "^1.0.0" + tiny-warning "^1.0.2" + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.1, mkdirp@~0.5.1: + version "0.5.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" + integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== + dependencies: + minimist "^1.2.5" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nan@^2.12.1: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-addon-api@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.1.tgz#cf813cd69bb8d9100f6bdca6755fc268f54ac492" + integrity sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ== + +node-emoji@^1.8.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" + integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== + dependencies: + lodash.toarray "^4.4.0" + +node-forge@^0.7.1: + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" + integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== + +node-libs-browser@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^3.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.1" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.11.0" + vm-browserify "^1.0.1" + +node-releases@^1.1.52: + version "1.1.52" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.52.tgz#bcffee3e0a758e92e44ecfaecd0a47554b0bcba9" + integrity sha512-snSiT1UypkgGt2wxPqS6ImEUICbNCMb31yaxWrOLXjhlt2z2/IBpaOxzONExqSm4y5oLnAqjjRWu+wsDzK5yNQ== + dependencies: + semver "^6.3.0" + +normalize-html-whitespace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/normalize-html-whitespace/-/normalize-html-whitespace-1.0.0.tgz#5e3c8e192f1b06c3b9eee4b7e7f28854c7601e34" + integrity sha512-9ui7CGtOOlehQu0t/OhhlmDyc71mKVlv+4vF+me4iZLPrNtRL2xoquEdfZxasC/bdQi/Hr3iTrpyRKIG+ocabA== + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-url@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" + integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== + +normalize.css@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" + integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== + +nth-check@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + +nwsapi@^2.1.3: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-inspect@~1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.4.1.tgz#37ffb10e71adaf3748d05f713b4c9452f402cbc4" + integrity sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +object.values@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + +opn@^5.1.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" + integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== + dependencies: + is-wsl "^1.1.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +ora@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-2.1.0.tgz#6caf2830eb924941861ec53a173799e008b51e5b" + integrity sha512-hNNlAd3gfv/iPmsNxYoAPLvxg7HuPozww7fFonMZvL84tP6Ox5igfk5j/+a9rtJJwqMgKK+JgWsAQik5o0HTLA== + dependencies: + chalk "^2.3.1" + cli-cursor "^2.1.0" + cli-spinners "^1.1.0" + log-symbols "^2.2.0" + strip-ansi "^4.0.0" + wcwidth "^1.0.1" + +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= + +p-limit@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@^0.2.5: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= + +pako@~1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parcel-bundler@^1.12.4: + version "1.12.4" + resolved "https://registry.yarnpkg.com/parcel-bundler/-/parcel-bundler-1.12.4.tgz#31223f4ab4d00323a109fce28d5e46775409a9ee" + integrity sha512-G+iZGGiPEXcRzw0fiRxWYCKxdt/F7l9a0xkiU4XbcVRJCSlBnioWEwJMutOCCpoQmaQtjB4RBHDGIHN85AIhLQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/core" "^7.4.4" + "@babel/generator" "^7.4.4" + "@babel/parser" "^7.4.4" + "@babel/plugin-transform-flow-strip-types" "^7.4.4" + "@babel/plugin-transform-modules-commonjs" "^7.4.4" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/preset-env" "^7.4.4" + "@babel/runtime" "^7.4.4" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.4" + "@babel/types" "^7.4.4" + "@iarna/toml" "^2.2.0" + "@parcel/fs" "^1.11.0" + "@parcel/logger" "^1.11.1" + "@parcel/utils" "^1.11.0" + "@parcel/watcher" "^1.12.1" + "@parcel/workers" "^1.11.0" + ansi-to-html "^0.6.4" + babylon-walk "^1.0.2" + browserslist "^4.1.0" + chalk "^2.1.0" + clone "^2.1.1" + command-exists "^1.2.6" + commander "^2.11.0" + core-js "^2.6.5" + cross-spawn "^6.0.4" + css-modules-loader-core "^1.1.0" + cssnano "^4.0.0" + deasync "^0.1.14" + dotenv "^5.0.0" + dotenv-expand "^5.1.0" + envinfo "^7.3.1" + fast-glob "^2.2.2" + filesize "^3.6.0" + get-port "^3.2.0" + htmlnano "^0.2.2" + is-glob "^4.0.0" + is-url "^1.2.2" + js-yaml "^3.10.0" + json5 "^1.0.1" + micromatch "^3.0.4" + mkdirp "^0.5.1" + node-forge "^0.7.1" + node-libs-browser "^2.0.0" + opn "^5.1.0" + postcss "^7.0.11" + postcss-value-parser "^3.3.1" + posthtml "^0.11.2" + posthtml-parser "^0.4.0" + posthtml-render "^1.1.3" + resolve "^1.4.0" + semver "^5.4.1" + serialize-to-js "^3.0.0" + serve-static "^1.12.4" + source-map "0.6.1" + terser "^3.7.3" + v8-compile-cache "^2.0.0" + ws "^5.1.1" + +parse-asn1@^5.0.0: + version "5.1.5" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" + integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== + dependencies: + asn1.js "^4.0.0" + browserify-aes "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse5@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" + integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + +pbkdf2@^3.0.3: + version "3.0.17" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" + integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +physical-cpu-count@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/physical-cpu-count/-/physical-cpu-count-2.0.0.tgz#18de2f97e4bf7a9551ad7511942b5496f7aba660" + integrity sha1-GN4vl+S/epVRrXURlCtUlverpmA= + +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +pn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" + integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +postcss-calc@^7.0.1: + version "7.0.2" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.2.tgz#504efcd008ca0273120568b0792b16cdcde8aac1" + integrity sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ== + dependencies: + postcss "^7.0.27" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" + +postcss-colormin@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" + integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== + dependencies: + browserslist "^4.0.0" + color "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-convert-values@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" + integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-discard-comments@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" + integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== + dependencies: + postcss "^7.0.0" + +postcss-discard-duplicates@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" + integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== + dependencies: + postcss "^7.0.0" + +postcss-discard-empty@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" + integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== + dependencies: + postcss "^7.0.0" + +postcss-discard-overridden@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" + integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== + dependencies: + postcss "^7.0.0" + +postcss-functions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-functions/-/postcss-functions-3.0.0.tgz#0e94d01444700a481de20de4d55fb2640564250e" + integrity sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4= + dependencies: + glob "^7.1.2" + object-assign "^4.1.1" + postcss "^6.0.9" + postcss-value-parser "^3.3.0" + +postcss-js@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-2.0.3.tgz#a96f0f23ff3d08cec7dc5b11bf11c5f8077cdab9" + integrity sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w== + dependencies: + camelcase-css "^2.0.1" + postcss "^7.0.18" + +postcss-merge-longhand@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" + integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== + dependencies: + css-color-names "0.0.4" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + stylehacks "^4.0.0" + +postcss-merge-rules@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" + integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + cssnano-util-same-parent "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + vendors "^1.0.0" + +postcss-minify-font-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" + integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-gradients@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" + integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + is-color-stop "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-params@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" + integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== + dependencies: + alphanum-sort "^1.0.0" + browserslist "^4.0.0" + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + uniqs "^2.0.0" + +postcss-minify-selectors@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" + integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== + dependencies: + alphanum-sort "^1.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +postcss-modules-extract-imports@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz#b614c9720be6816eaee35fb3a5faa1dba6a05ddb" + integrity sha1-thTJcgvmgW6u41+zpfqh26agXds= + dependencies: + postcss "^6.0.1" + +postcss-modules-local-by-default@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-scope@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-values@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" + integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= + dependencies: + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" + +postcss-nested@^4.1.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-4.2.1.tgz#4bc2e5b35e3b1e481ff81e23b700da7f82a8b248" + integrity sha512-AMayXX8tS0HCp4O4lolp4ygj9wBn32DJWXvG6gCv+ZvJrEa00GUxJcJEEzMh87BIe6FrWdYkpR2cuyqHKrxmXw== + dependencies: + postcss "^7.0.21" + postcss-selector-parser "^6.0.2" + +postcss-normalize-charset@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" + integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== + dependencies: + postcss "^7.0.0" + +postcss-normalize-display-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" + integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-positions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" + integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== + dependencies: + cssnano-util-get-arguments "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-repeat-style@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" + integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-string@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" + integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== + dependencies: + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-timing-functions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" + integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-unicode@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" + integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-url@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" + integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-whitespace@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" + integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-ordered-values@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" + integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== + dependencies: + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-reduce-initial@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" + integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + +postcss-reduce-transforms@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" + integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== + dependencies: + cssnano-util-get-match "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-selector-parser@6.0.2, postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" + integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" + integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== + dependencies: + dot-prop "^5.2.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" + integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== + dependencies: + is-svg "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + svgo "^1.0.0" + +postcss-unique-selectors@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" + integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== + dependencies: + alphanum-sort "^1.0.0" + postcss "^7.0.0" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + +postcss-value-parser@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" + integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg== + +postcss@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.1.tgz#000dbd1f8eef217aa368b9a212c5fc40b2a8f3f2" + integrity sha1-AA29H47vIXqjaLmiEsX8QLKo8/I= + dependencies: + chalk "^1.1.3" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@^6.0.1, postcss@^6.0.9: + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.18, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27: + version "7.0.27" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" + integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +posthtml-parser@^0.4.0, posthtml-parser@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.4.2.tgz#a132bbdf0cd4bc199d34f322f5c1599385d7c6c1" + integrity sha512-BUIorsYJTvS9UhXxPTzupIztOMVNPa/HtAm9KHni9z6qEfiJ1bpOBL5DfUOL9XAc3XkLIEzBzpph+Zbm4AdRAg== + dependencies: + htmlparser2 "^3.9.2" + +posthtml-render@^1.1.3, posthtml-render@^1.1.5: + version "1.2.0" + resolved "https://registry.yarnpkg.com/posthtml-render/-/posthtml-render-1.2.0.tgz#3df0c800a8bbb95af583a94748520469477addf4" + integrity sha512-dQB+hoAKDtnI94RZm/wxBUH9My8OJcXd0uhWmGh2c7tVtQ85A+OS3yCN3LNbFtPz3bViwBJXAeoi+CBGMXM0DA== + +posthtml@^0.11.2: + version "0.11.6" + resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.11.6.tgz#e349d51af7929d0683b9d8c3abd8166beecc90a8" + integrity sha512-C2hrAPzmRdpuL3iH0TDdQ6XCc9M7Dcc3zEW5BLerY65G4tWWszwv6nG/ksi6ul5i2mx22ubdljgktXCtNkydkw== + dependencies: + posthtml-parser "^0.4.1" + posthtml-render "^1.1.5" + +posthtml@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.12.0.tgz#6e2a2fcd774eaed1a419a95c5cc3a92b676a40a6" + integrity sha512-aNUEP/SfKUXAt+ghG51LC5MmafChBZeslVe/SSdfKIgLGUVRE68mrMF4V8XbH07ZifM91tCSuxY3eHIFLlecQw== + dependencies: + posthtml-parser "^0.4.1" + posthtml-render "^1.1.5" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.2.tgz#1ba8f3eb92231e769b7fcd7cb73ae1b6b74ade08" + integrity sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg== + +pretty-hrtime@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= + +private@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +prop-types@^15.6.2, prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +psl@^1.1.28: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +purgecss@^1.4.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-1.4.2.tgz#67ab50cb4f5c163fcefde56002467c974e577f41" + integrity sha512-hkOreFTgiyMHMmC2BxzdIw5DuC6kxAbP/gGOGd3MEsF3+5m69rIvUEPaxrnoUtfODTFKe9hcXjGwC6jcjoyhOw== + dependencies: + glob "^7.1.3" + postcss "^7.0.14" + postcss-selector-parser "^6.0.0" + yargs "^14.0.0" + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +quote-stream@^1.0.1, quote-stream@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/quote-stream/-/quote-stream-1.0.2.tgz#84963f8c9c26b942e153feeb53aae74652b7e0b2" + integrity sha1-hJY/jJwmuULhU/7rU6rnRlK34LI= + dependencies: + buffer-equal "0.0.1" + minimist "^1.1.3" + through2 "^2.0.0" + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +react-dom@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" + integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.19.1" + +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.9.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-redux@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d" + integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA== + dependencies: + "@babel/runtime" "^7.5.5" + hoist-non-react-statics "^3.3.0" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" + +react-router-dom@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" + integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.1.2" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" + integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.3.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" + integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + +readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.3, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.1.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +reduce-css-calc@^2.1.6: + version "2.1.7" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2" + integrity sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA== + dependencies: + css-unit-converter "^1.1.1" + postcss-value-parser "^3.3.0" + +redux-devtools-extension@^2.13.8: + version "2.13.8" + resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1" + integrity sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg== + +redux-immutable-state-invariant@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/redux-immutable-state-invariant/-/redux-immutable-state-invariant-2.1.0.tgz#308fd3cc7415a0e7f11f51ec997b6379c7055ce1" + integrity sha512-3czbDKs35FwiBRsx/3KabUk5zSOoTXC+cgVofGkpBNv3jQcqIe5JrHcF5AmVt7B/4hyJ8MijBIpCJ8cife6yJg== + dependencies: + invariant "^2.1.0" + json-stringify-safe "^5.0.1" + +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + +redux@^4.0.0: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + +regenerate-unicode-properties@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-runtime@^0.13.4: + version "0.13.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" + integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + +regenerator-transform@^0.14.2: + version "0.14.4" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" + integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw== + dependencies: + "@babel/runtime" "^7.8.4" + private "^0.1.8" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexpu-core@^4.6.0, regexpu-core@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" + integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.2.0" + +regjsgen@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" + integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== + +regjsparser@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" + integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== + dependencies: + jsesc "~0.5.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +request-promise-core@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" + integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== + dependencies: + lodash "^4.17.15" + +request-promise-native@^1.0.5: + version "1.0.8" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" + integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== + dependencies: + request-promise-core "1.1.3" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" + integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@^1.1.5, resolve@^1.14.2, resolve@^1.3.2, resolve@^1.4.0: + version "1.15.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" + integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + dependencies: + path-parse "^1.0.6" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rgb-regex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" + integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= + +rgba-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" + integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= + +rimraf@^2.6.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +saxes@^3.1.9: + version "3.1.11" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" + integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== + dependencies: + xmlchars "^2.1.1" + +scheduler@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" + integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^5.4.1, semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serialize-to-js@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/serialize-to-js/-/serialize-to-js-3.1.1.tgz#b3e77d0568ee4a60bfe66287f991e104d3a1a4ac" + integrity sha512-F+NGU0UHMBO4Q965tjw7rvieNVjlH6Lqi2emq/Lc9LUURYJbiCzmpi4Cy1OOjjVPtxu0c+NE85LU6968Wko5ZA== + +serve-static@^1.12.4: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shallow-copy@~0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170" + integrity sha1-QV9CcC1z2BAzApLMXuhurhoRoXA= + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@~0.5.10, source-map-support@~0.5.12: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +static-eval@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.0.5.tgz#f0782e66999c4b3651cda99d9ce59c507d188f71" + integrity sha512-nNbV6LbGtMBgv7e9LFkt5JV8RVlRsyJrphfAt9tOtBBW/SfnzZDf2KnS72an8e434A+9e/BmJuTxeGPvrAK7KA== + dependencies: + escodegen "^1.11.1" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +static-module@^2.2.0: + version "2.2.5" + resolved "https://registry.yarnpkg.com/static-module/-/static-module-2.2.5.tgz#bd40abceae33da6b7afb84a0e4329ff8852bfbbf" + integrity sha512-D8vv82E/Kpmz3TXHKG8PPsCPg+RAX6cbCOyvjM6x04qZtQ47EtJFVwRsdov3n5d6/6ynrOY9XB4JkaZwB2xoRQ== + dependencies: + concat-stream "~1.6.0" + convert-source-map "^1.5.1" + duplexer2 "~0.1.4" + escodegen "~1.9.0" + falafel "^2.1.0" + has "^1.0.1" + magic-string "^0.22.4" + merge-source-map "1.0.4" + object-inspect "~1.4.0" + quote-stream "~1.0.2" + readable-stream "~2.3.3" + shallow-copy "~0.0.1" + static-eval "^2.0.0" + through2 "~2.0.3" + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +stream-browserify@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-http@^2.7.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string.prototype.trimleft@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" + integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" + integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string_decoder@^1.0.0, string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +stylehacks@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" + integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= + dependencies: + has-flag "^1.0.0" + +supports-color@^5.3.0, supports-color@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +svgo@^1.0.0, svgo@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" + css-select "^2.0.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" + mkdirp "~0.5.1" + object.values "^1.1.0" + sax "~1.2.4" + stable "^0.1.8" + unquote "~1.1.1" + util.promisify "~1.0.0" + +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + +symbol-tree@^3.2.2: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +tailwindcss@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-1.2.0.tgz#5df317cebac4f3131f275d258a39da1ba3a0f291" + integrity sha512-CKvY0ytB3ze5qvynG7qv4XSpQtFNGPbu9pUn8qFdkqgD8Yo/vGss8mhzbqls44YCXTl4G62p3qVZBj45qrd6FQ== + dependencies: + autoprefixer "^9.4.5" + bytes "^3.0.0" + chalk "^3.0.0" + detective "^5.2.0" + fs-extra "^8.0.0" + lodash "^4.17.15" + node-emoji "^1.8.1" + normalize.css "^8.0.1" + postcss "^7.0.11" + postcss-functions "^3.0.0" + postcss-js "^2.0.0" + postcss-nested "^4.1.1" + postcss-selector-parser "^6.0.0" + pretty-hrtime "^1.0.3" + reduce-css-calc "^2.1.6" + resolve "^1.14.2" + +terser@^3.7.3: + version "3.17.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" + integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ== + dependencies: + commander "^2.19.0" + source-map "~0.6.1" + source-map-support "~0.5.10" + +terser@^4.3.9: + version "4.6.7" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.7.tgz#478d7f9394ec1907f0e488c5f6a6a9a2bad55e72" + integrity sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + +through2@^2.0.0, through2@~2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +timers-browserify@^2.0.4: + version "2.0.11" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" + integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== + dependencies: + setimmediate "^1.0.4" + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +tiny-inflate@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" + integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== + +tiny-invariant@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + +tiny-warning@^1.0.0, tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= + dependencies: + punycode "^2.1.0" + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +typescript@^3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== + +uncss@^0.17.2: + version "0.17.3" + resolved "https://registry.yarnpkg.com/uncss/-/uncss-0.17.3.tgz#50fc1eb4ed573ffff763458d801cd86e4d69ea11" + integrity sha512-ksdDWl81YWvF/X14fOSw4iu8tESDHFIeyKIeDrK6GEVTQvqJc1WlOEXqostNwOCi3qAj++4EaLsdAgPmUbEyog== + dependencies: + commander "^2.20.0" + glob "^7.1.4" + is-absolute-url "^3.0.1" + is-html "^1.1.0" + jsdom "^14.1.0" + lodash "^4.17.15" + postcss "^7.0.17" + postcss-selector-parser "6.0.2" + request "^2.88.0" + +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== + +unicode-trie@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-0.3.1.tgz#d671dddd89101a08bac37b6a5161010602052085" + integrity sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU= + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +util@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= + dependencies: + inherits "2.0.1" + +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + dependencies: + inherits "2.0.3" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-compile-cache@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" + integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== + +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + +vendors@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" + integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vlq@^0.2.2: + version "0.2.3" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" + integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== + +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + +w3c-hr-time@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" + integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== + dependencies: + domexception "^1.0.1" + webidl-conversions "^4.0.2" + xml-name-validator "^3.0.0" + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^5.1.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== + dependencies: + async-limiter "~1.0.0" + +ws@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yargs-parser@^15.0.1: + version "15.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.1.tgz#54786af40b820dcb2fb8025b11b4d659d76323b3" + integrity sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^14.0.0: + version "14.2.3" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.3.tgz#1a1c3edced1afb2a2fea33604bc6d1d8d688a414" + integrity sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg== + dependencies: + cliui "^5.0.0" + decamelize "^1.2.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^15.0.1" diff --git a/users/wpcarro/buildHaskell/default.nix b/users/wpcarro/buildHaskell/default.nix new file mode 100644 index 000000000..5958b1ea2 --- /dev/null +++ b/users/wpcarro/buildHaskell/default.nix @@ -0,0 +1,31 @@ +{ pkgs, ... }: + +{ + # Create a nix-shell for Haskell development. + shell = { deps }: let + ghc = pkgs.haskellPackages.ghcWithPackages (hpkgs: deps hpkgs); + in pkgs.mkShell { + buildInputs = [ghc]; + }; + + # Build a Haskell executable. This assumes a project directory with a + # top-level Main.hs. + # - `name`: You can find the result at ./result/$name + # - `srcs`: Will be passed to `srcs` field of `pkgs.stdenv.mkDerivation`. + # - `deps`: A function that accepts `hpkgs` and returns a list of Haskell + # - `ghcExtensions`: A list of strings representing the language extensions to + # use. + program = { name, srcs, deps, ghcExtensions }: let + ghc = pkgs.haskellPackages.ghcWithPackages (hpkgs: deps hpkgs); + in pkgs.stdenv.mkDerivation { + name = name; + buildInputs = []; + srcs = srcs; + buildPhase = '' + ${ghc}/bin/ghc -Wall Main.hs ${pkgs.lib.concatMapStrings (x: "-X${x} ") ghcExtensions} + ''; + installPhase = '' + mkdir -p $out && mv Main $out/${name} + ''; + }; +} diff --git a/users/wpcarro/ci/pipelines/post-receive.nix b/users/wpcarro/ci/pipelines/post-receive.nix new file mode 100644 index 000000000..456d546af --- /dev/null +++ b/users/wpcarro/ci/pipelines/post-receive.nix @@ -0,0 +1,56 @@ +{ briefcase, pkgs, ... }: + +let + inherit (builtins) fetchGit path toJSON; + inherit (briefcase.emacs) initEl runScript; + + elispLintSrc = fetchGit { + url = "https://github.com/gonewest818/elisp-lint"; + rev = "2b645266be8010a6a49c6d0ebf6a3ad5bd290ff4"; + }; + + pipeline.steps = [ + { + key = "lint-secrets"; + command = "${pkgs.git-secrets}/bin/git-secrets --scan-history"; + label = ":broom: lint secrets"; + } + { + key = "build-briefcase"; + command = '' + nix-build . -I briefcase="$(pwd)" --no-out-link --show-trace + ''; + label = ":nix: build briefcase"; + depends_on = "lint-secrets"; + } + { + key = "init-emacs"; + command = let + scriptEl = path { + path = ./script.el; + name = "script.el"; + }; + runScriptEl = runScript { + script = scriptEl; + briefcasePath = "$(pwd)"; + }; + in "${runScriptEl} ${initEl}"; + label = ":gnu: initialize Emacs"; + depends_on = "build-briefcase"; + } + { + key = "build-socrates"; + command = '' + nix-build '' \ + -I briefcase="$(pwd)" \ + -I nixpkgs=/var/lib/buildkite-agent-socrates/nixpkgs-channels \ + -I nixos-config=nixos/socrates/default.nix \ + -A system \ + --no-out-link \ + --show-trace + ''; + label = ":nix: build socrates"; + depends_on = "build-briefcase"; + } + ]; +in pkgs.writeText "pipeline.yaml" (toJSON pipeline) diff --git a/users/wpcarro/ci/pipelines/script.el b/users/wpcarro/ci/pipelines/script.el new file mode 100644 index 000000000..da079b64b --- /dev/null +++ b/users/wpcarro/ci/pipelines/script.el @@ -0,0 +1,44 @@ +;; This script initializes Emacs and exits with either a zero or non-zero status +;; depending on whether or not Emacs initialized without logging warnings or +;; encountering errors. +;; +;; This script reads the location of init.el as the last argument in `argv'. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'f) +(require 'dash) +(require 'buffer) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Script +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar init-el-path (-last-item argv) + "Path to the init.el file that this script attempts to load.") + +(prelude-assert (f-exists? init-el-path)) + +(condition-case err + (load init-el-path) + (error + (message "Encountered an error while attempting to load init.el: %s" err) + (kill-emacs 1))) + +(when (buffer-exists? "*Errors*") + (progn + (with-current-buffer "*Errors*" + (message "Encountered errors in *Errors* buffer: %s" (buffer-string))) + (kill-emacs 1))) + +(when (buffer-exists? "*Warnings*") + (progn + (with-current-buffer "*Warnings*" + (message "Encountered warnings in *Warnings* buffer: %s" (buffer-string))) + (kill-emacs 1))) + +(message "Successfully init'd Emacs without encountering errors or warnings!") +(kill-emacs 0) diff --git a/users/wpcarro/ci/secret-patterns.txt b/users/wpcarro/ci/secret-patterns.txt new file mode 100644 index 000000000..cbf58a1e7 --- /dev/null +++ b/users/wpcarro/ci/secret-patterns.txt @@ -0,0 +1,9 @@ +(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16} +("|')?(AWS|aws|Aws)?_?(SECRET|secret|Secret)?_?(ACCESS|access|Access)?_?(KEY|key|Key)("|')?\s*(:|=>|=)\s*("|')?[A-Za-z0-9/\+=]{40}("|')? +("|')?(AWS|aws|Aws)?_?(ACCOUNT|account|Account)_?(ID|id|Id)?("|')?\s*(:|=>|=)\s*("|')?[0-9]{4}\-?[0-9]{4}\-?[0-9]{4}("|')? +AIza[0-9A-Za-z_-]{35} +[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com +(^|[^0-9A-Za-z/+])1/[0-9A-Za-z_-]{43} +(^|[^0-9A-Za-z/+])1/[0-9A-Za-z_-]{64} +ya29\.[0-9A-Za-z_-]+ +(sk|pk)_(test|live)_[a-zA-Z0-9]{99} diff --git a/users/wpcarro/configs/.config/fish/prompt.fish b/users/wpcarro/configs/.config/fish/prompt.fish new file mode 100644 index 000000000..58d22dab5 --- /dev/null +++ b/users/wpcarro/configs/.config/fish/prompt.fish @@ -0,0 +1,87 @@ +# When the Emacs SSH client, Tramp, connects to a remote host that uses Fish, +# it's important to keep the shell prompt simple so that Tramp can parse it. +if test "$TERM" = "dumb" + function fish_prompt + echo "\$ " + end + function fish_right_prompt; end + function fish_greeting; end + function fish_title; end +else + function fish_prompt + # My custom prompt. + # + # Design objectives: + # - max-length <= 80 characters + # - minimal + # - no dependencies (well, you know what I mean) + # + # Components + # - ssh connection + # - user + # - host + # - git repo + # - git branch + # - lambda character as prompt + + # Cache status before we overwrite it. + set -l last_status $status + + # Colors + set -l color_inactive (set_color red --bold) + set -l color_active (set_color green --bold) + set -l color_normal (set_color normal) + + # SSH information + if set -q SSH_CLIENT; or set -q SSH_TTY + echo -en "$color_active \bssh ✓ [$color_normal$USER@"(hostname)"$color_active]$color_normal" + else + echo -en "$color_inactive \bssh ✗ [$color_normal$USER@"(hostname)"$color_inactive]$color_normal" + end + + # Separator + echo -n " " + + # Git information + set -l git_repo (git rev-parse --show-toplevel 2>/dev/null) + set -l git_status $status + + if [ (realpath .) = "/" ] + set -g dir_path (realpath .) + else if [ (realpath ..) = "/" ] + set -g dir_path (realpath .) + else + set -g dir_path (echo (basename (realpath ..))"/"(basename (realpath .))) + end + + if test $git_status -eq 0 + set -l git_repo_name (basename (git rev-parse --show-toplevel)) + set -l git_branch (git branch 2>/dev/null | grep '^\*' | cut -d' ' -f2-) + echo -en "$color_active \bgit ✓ [$color_normal$git_branch$color_active|$color_normal$git_repo_name$color_active|$color_normal$dir_path$color_active]$color_normal" + else + echo -en "$color_inactive \bgit ✗ [$color_normal$dir_path$color_inactive]$color_normal" + end + + # Newline + echo + + # Handle root vs non-root + if [ "$USER" = "root" ] + set -g prompt_sigil "#" + else + set -g prompt_sigil "λ" + end + + set -l time (date +"%T") + if test $last_status -eq 0 + set -l color_prompt (set_color white --bold) + echo -n "$time$color_prompt $prompt_sigil$color_normal " + else + set -l color_prompt (set_color red --bold) + echo -n "$time$color_prompt $prompt_sigil$color_normal " + end + end + function fish_right_prompt; end + function fish_greeting; end + function fish_title; end +end diff --git a/users/wpcarro/configs/.config/nixpkgs/config.nix b/users/wpcarro/configs/.config/nixpkgs/config.nix new file mode 100644 index 000000000..1dd1750ae --- /dev/null +++ b/users/wpcarro/configs/.config/nixpkgs/config.nix @@ -0,0 +1,3 @@ +{ + allowUnfree = true; +} diff --git a/users/wpcarro/configs/.config/nvim/init.vim b/users/wpcarro/configs/.config/nvim/init.vim new file mode 100644 index 000000000..57cfe7ea6 --- /dev/null +++ b/users/wpcarro/configs/.config/nvim/init.vim @@ -0,0 +1,668 @@ +" -- BEGIN: Vundle config -- +set nocompatible " be iMproved, required +filetype off " required + +" set the runtime path to include Vundle and initialize +" share Vundle between vim and neovim +set rtp+=~/.vim/bundle/Vundle.vim +set rtp+=~/.config/nvim/bundle/Vundle.vim +call vundle#begin() +" alternatively, pass a path where Vundle should install plugins +"call vundle#begin('~/some/path/here') + +" let Vundle manage Vundle, required +Plugin 'VundleVim/Vundle.vim' + +" Rust IDE features +Plugin 'racer-rust/vim-racer' + +set hidden +let g:racer_experimental_completer = 1 +autocmd FileType rust nmap gd (rust-def) +autocmd FileType rust nmap gs (rust-def-split) +autocmd FileType rust nmap gx (rust-def-vertical) +autocmd FileType rust nmap gd (rust-doc) + +Plugin 'xolox/vim-misc' + +" The following are examples of different formats supported. +" Keep Plugin commands between vundle#begin/end. + +" Displays git information in airline. +Plugin 'tpope/vim-fugitive' + +" easier file navigation +Plugin 'tpope/vim-vinegar' + +" Displays git-tracked C*UD ops within gutter. +Plugin 'airblade/vim-gitgutter' + +" Fuzzy-finder +Plugin 'kien/ctrlp.vim' + +" Grep file contents +Plugin 'mileszs/ack.vim' + +" Syntax and other light-weight suppor for a variety of languages +Plugin 'sheerun/vim-polyglot' + +" Themes +Plugin 'deviantfero/wpgtk.vim' +Plugin 'rainglow/vim' + + +" Executes shell commands and pipes output into new Vim buffer. +Plugin 'sjl/clam.vim' + +" Multiple cursors for simultaneous edits. +" NOTE: use to run miltiple cursors not +Plugin 'terryma/vim-multiple-cursors' + +" Visualize buffers +Plugin 'vim-airline/vim-airline' +Plugin 'vim-airline/vim-airline-themes' + +" Visually align assignments +Plugin 'godlygeek/tabular' + +" Visually Highlight and comment code. +Plugin 'tpope/vim-commentary' + +" Macros for quotes, parens, etc. +Plugin 'tpope/vim-surround' + +" Allows Plugins to be repeated with `.` character +Plugin 'tpope/vim-repeat' + +" Pairs of mappings +Plugin 'tpope/vim-unimpaired' + +" LISPs support +Plugin 'guns/vim-sexp' +Plugin 'tpope/vim-sexp-mappings-for-regular-people' +let g:sexp_enable_insert_mode_mappings = 0 +let g:sexp_filetypes = '' + +" Seamlessly navigate Vim and Tmux with similar bindings. +Plugin 'christoomey/vim-tmux-navigator' + +" Async `:make` for code linting etc. +Plugin 'neomake/neomake' + +" Better buffer mgt than CtrlP +Plugin 'yegappan/mru' + +Plugin 'zanglg/nova.vim' + +" Emulates Emacs's Helm Swoop search +Plugin 'pelodelfuego/vim-swoop' + +" Transparent encryption + decryption +Plugin 'jamessan/vim-gnupg' + +" Javascript auto-formatting +" Plugin 'prettier/vim-prettier', { +" \ 'do': 'yarn install', + " \ 'for': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'graphql', 'markdown'] } + +" Support Org mode +Plugin 'jceb/vim-orgmode' + +" Autocompletion +Plugin 'junegunn/fzf' + +" Text objects made easy +Plugin 'kana/vim-textobj-user' + +" Elixir text objects +Plugin 'andyl/vim-textobj-elixir' + +" Making HTML editing faster +Plugin 'mattn/emmet-vim' + +" Snippets for all languages +Plugin 'honza/vim-snippets' + +" Automatic bracket insertion +Plugin 'jiangmiao/auto-pairs' + +" Linting & error warnings +Plugin 'vim-syntastic/syntastic' + +" Angular.js support +Plugin 'burnettk/vim-angular' + +" Asynchronous Linting Engine +Plugin 'w0rp/ale' + +call vundle#end() " required +filetype plugin indent on " required +" Put your non-Plugin stuff after this line +" -- END: Vundle config -- + +" Changes to character. +let mapleader = " " + + +" Highlight column width +set textwidth=80 +set colorcolumn=+0 + +" autoreload a file when it changes on disk +set autoread + +" default to case-insensitive searching +set ignorecase + +" JSX configuration +let g:jsx_ext_required = 0 + + +autocmd FileType reason nnoremap gd :call LanguageClient_textDocument_definition() +autocmd FileType reason nnoremap gf :call LanguageClient_textDocument_formatting() +autocmd FileType reason nnoremap gh :call LanguageClient_textDocument_hover() +autocmd FileType reason nnoremap gr :call LanguageClient_textDocument_rename() + +" Replace with G for faster navigation +nnoremap G +onoremap G +vnoremap G + +" Mirror ZLE KBD +inoremap :echo "Working" + +" Syntastic configuration +set statusline+=%#warningmsg# +set statusline+=%{SyntasticStatuslineFlag()} +set statusline+=%* + +let g:syntastic_always_populate_loc_list = 1 +let g:syntastic_auto_loc_list = 1 +let g:syntastic_check_on_open = 1 +let g:syntastic_check_on_wq = 0 +" let g:syntastic_javascript_checkers = ['eslint'] +let g:syntastic_javascript_eslint_generic = 1 +" this is a hack to prevent a false negative +" https://github.com/vim-syntastic/syntastic/issues/1692 +" let g:syntastic_javascript_eslint_exec = '/bin/ls' +" let g:syntastic_javascript_eslint_exe = 'npx eslint' +" let g:syntastic_javascript_eslint_args = '-f compact' + +" javascript autocompletion +" autocmd FileType javascript setlocal omnifunc=javascriptcomplete#CompleteJS +" autocmd FileType javascript nnoremap gf :Prettier + +" Maximize the current window +" Similar to Tmux mapping alt-z in my tmux.conf +nnoremap t% :tab sp + +" Allow C-g to act like C-c the way it does in Emacs +cnoremap + +" Prettier configuration +" let g:prettier#exec_cmd_async = 1 +" force Prettier to run on files even without the @format pragma +" let g:prettier#autoformat = 0 + + +" Basic settings +" Thin cursor on INSERT mode +if has('nvim') + let $NVIM_TUI_ENABLE_CURSOR_SHAPE = 1 +endif + +set number +set nowrap +set tabstop=2 +set expandtab +set shiftwidth=2 +set background=dark + +syntax enable +colorscheme peacock + +" Vim in terminal cannot have a different font from the one set within your +" terminal. However, this setting will set the font for the GUI version. +if has('gui_running') + set guifont=Operator\ Mono:h12 +endif + +if has('termguicolors') + set termguicolors +endif + +if &term =~# '^screen' + let &t_8f = "\[38;2;%lu;%lu;%lum" + let &t_8b = "\[48;2;%lu;%lu;%lum" +endif + +set history=1000 +set undolevels=1000 + +set t_Co=255 + +" Support italics +highlight Comment cterm=italic + + +" quickly edit popular configuration files +nnoremap ev :vsplit $MYVIMRC +nnoremap ee :vsplit ~/.emacs.d/init.el +nnoremap ez :vsplit ~/.zshrc +nnoremap ea :vsplit ~/aliases.zsh +nnoremap ef :vsplit ~/functions.zsh +nnoremap el :vsplit ~/variables.zsh +nnoremap ex :vsplit ~/.Xresources + +" quickly source your vimrc +nnoremap sv :source $MYVIMRC + +" quickly edit your snippets +nnoremap es :vsplit:edit ~/.vim/bundle/vim-snippets/snippets/reason.snippets + + +" Auto resize window splits +autocmd VimResized * wincmd = + + +" Neomake Settings +autocmd! BufWritePost * Neomake + +" Elixir linting +let g:neomake_elixir_credo_maker = { + \ 'exe': 'mix', + \ 'args': ['credo', 'list', '%:p', '--format=oneline'], + \ 'errorformat': + \ '%W[F] %. %f:%l:%c %m,' . + \ '%W[F] %. %f:%l %m,' . + \ '%W[R] %. %f:%l:%c %m,' . + \ '%W[R] %. %f:%l %m,' . + \ '%I[C] %. %f:%l:%c %m,' . + \ '%I[C] %. %f:%l %m,' . + \ '%-Z%.%#' + \ } + + +let g:neomake_elixir_enabled_makers = ['mix', 'credo'] + +augroup my_error_signs + au! + autocmd ColorScheme * hi NeomakeErrorSign ctermfg=203 guifg=#ff5f5f + autocmd ColorScheme * hi NeomakeWarningSign ctermfg=209 guifg=#ffaf00 + autocmd ColorScheme * hi NeomakeInfoSign ctermfg=183 guifg=#dfafff + autocmd ColorScheme * hi NeomakeMessageSign ctermfg=27 guifg=#0087ff +augroup END + + +" templates +if has("autocmd") + autocmd BufNewFile *.c 0r ~/.config/nvim/templates/boilerplate.c + autocmd BufNewFile *.rs 0r ~/.config/nvim/templates/boilerplate.rs +endif + +let g:neomake_error_sign = { + \ 'text': '>>', + \ 'texthl': 'NeoMakeErrorSign', + \ } + +let g:neomake_warning_sign = { + \ 'text': '>>', + \ 'texthl': 'NeoMakeWarningSign', + \ } + +let g:neomake_info_sign = { + \ 'text': '>>', + \ 'texthl': 'NeoMakeInfoSign', + \ } + +let g:neomake_message_sign = { + \ 'text': '>>', + \ 'texthl': 'NeoMakeMessageSign', + \ } + +function! LocationPrevious() + try + lprev + catch /^Vim\%((\a\+)\)\=:E553/ + llast + endtry +endfunction + +function! LocationNext() + try + lnext + catch /^Vim\%((\a\+)\)\=:E553/ + lfirst + endtry +endfunction + +nnoremap [ :call LocationPrevious() +nnoremap ] :call LocationNext() + + +" Alchemist settings +let g:alchemist#elixir_erlang_src = '/usr/local/share/src' + + +" Airline Settings +" Enables the list of buffers. +let g:airline#extensions#tabline#enabled = 0 + +" Buffer numbers alongside files +let g:airline#extensions#tabline#buffer_nr_show = 0 + +" Shows the filename only. +let g:airline#extensions#tabline#fnamemod = ':t' + +" Allow glyphs in airline +let g:airline_powerline_fonts = 1 + +" Change Airline theme +let g:airline_theme = 'hybrid' + + +" Vim-Swoop Settings +" Edits colorscheme +let g:swoopHighlight = ["hi! link SwoopBufferLineHi Warning", "hi! link SwoopPatternHi Error"] + + +" Jump to buffers. +nmap :1b +nmap :2b +nmap :3b +nmap :4b +nmap :5b +nmap :6b +nmap :7b +nmap :8b +nmap :9b + + +" It's the twenty-first century...no swaps. +set noswapfile + + +" Allow visual tab completion in command mode +set wildmenu + + +" Show Vim commands as they're being input. +set showcmd + + +" Code folding +" set foldmethod=indent +" set foldnestmax=10 +" set nofoldenable +" set foldlevel=4 + + +" emulate ci" and ci' behavior +nnoremap ci( f(%ci( +nnoremap ci[ f[%ci[ + + +" extend functionality of & scrolling +nnoremap j +vnoremap j +nnoremap k +vnoremap k + + +" Opens all folds within the buffer +" nnoremap ZZ zR + +" Closes all folds within the buffer +" nnoremap zz zM + +" Opens all folds beneath the cursor +" NOTE: j is the character to go down +" nnoremap zJ zO + +" Opens single fold beneath the cursor +" NOTE: j is the character to go down +" nnoremap zj zo + +" Opens single fold beneath the cursor +" NOTE: k is the character to go down +" nnoremap zK zC + +" Opens single fold beneath the cursor +" NOTE: k is the character to go down +" nnoremap zk zc + + +" Save shortcut +nnoremap :w + + +" Switch to MRU'd buffer +nnoremap + + +" Alternative MRU to CtrlP MRU +nnoremap b :MRU + + +" Supports mouse interaction. +set mouse=a + + +" Highlights matches during a search. +set hlsearch + +" Clear highlight +noremap h :nohlsearch:echo + + +" backspace settings +set backspace=2 +set backspace=indent,eol,start + + +" Javascript specific variables +let g:javascript_plugin_jsdoc = 1 + +" GlobalListchars +set list +set listchars=tab:··,trail:·,nbsp:· + + +" Keeps everything concealed at all times. Even when cursor is on the word. +set conceallevel=1 +set concealcursor=nvic + + +" map jk to +inoremap jk + + +" Hybrid mode for Vim +inoremap I +inoremap A + +inoremap +inoremap + +inoremap +inoremap +inoremap +inoremap + +" temporarily disable in normal mode so it doesn't attempt to index all of +" Google3. +nnoremap :echo "You are attempting to index all of Google3. Aborting..." + +" tab maintenence +nnoremap :tabnew +nnoremap :tabclose +nnoremap :tabnext +nnoremap :tabprevious + +" Manage Vertical and Horizontal splits +nnoremap sl :vsl +nnoremap sh :vs +nnoremap sj :spj +nnoremap sk :sp + + +" Delete (i.e. "close") the currently opened buffer +" TODO: unless it's a split window, which should be :q +nnoremap q :bdelete + + +" Set CtrlP runtime path +set runtimepath^=~/.vim/bundle/ctrlp.vim + + +" Pane movement +let g:tmux_navigator_no_mappings = 1 + +nnoremap :TmuxNavigateLeft +nnoremap :TmuxNavigateDown +nnoremap :TmuxNavigateUp +nnoremap :TmuxNavigateRight +nnoremap :q + +" make Y do what is intuitive given: +" D: deletes until EOL +" C: changes until EOL +" Y: (should) yank until EOL +nnoremap Y y$ + + +" scrolling and maintaing mouse position +" nnoremap j +" nnoremap k + + +" remap redo key that is eclipsed by `rotate` currently +nnoremap U :redo + + +" Define highlighting groups +" NOTE: The ANSII aliases for colors will change when iTerm2 settings are +" changed. +highlight InterestingWord1 ctermbg=Magenta ctermfg=Black +highlight InterestingWord2 ctermbg=Blue ctermfg=Black + +" h1 highlighting +nnoremap 1 :execute '2match InterestingWord1 /\<\>/' +nnoremap x1 :execute '2match none' +vnoremap 1 :execute '2match InterestingWord1 /\<\>/' + +" h2 highlighting +nnoremap 2 :execute '3match InterestingWord2 /\<\>/' +nnoremap x2 :execute '3match none' + +"clear all highlighted groups +nnoremap xx :execute '2match none' :execute '3match none' hh + + +" pasteboard copy & paste +set clipboard+=unnamedplus + + +" Manage 80 char line limits +highlight OverLength1 ctermbg=Magenta ctermfg=Black +highlight OverLength2 ctermbg=LightMagenta ctermfg=Black +highlight OverLength3 ctermbg=White ctermfg=Black +" match OverLength3 /\%81v.\+/ +match OverLength2 /\%91v.\+/ +" match OverLength3 /\%101v.\+/ + +nnoremap w :w + + +" Resize split to 10,20,...,100 chars +" Uncomment the next lines for support at those sizes. +" These bindings interfere with the highlight groups, however. +" Increases the width of a vertical split. +" nnoremap 1 :vertical resize 10 +" nnoremap 2 :vertical resize 20 +nnoremap 3 :vertical resize 30 +nnoremap 4 :vertical resize 40 +nnoremap 5 :vertical resize 50 +nnoremap 6 :vertical resize 60 +nnoremap 7 :vertical resize 70 +nnoremap 8 :vertical resize 80 +nnoremap 9 :vertical resize 90 +nnoremap 0 :vertical resize 100 + + +" Increases the height of a horizontal split. +nnoremap v1 :resize 5 +nnoremap v2 :resize 10 +nnoremap v3 :resize 15 +nnoremap v4 :resize 20 +nnoremap v5 :resize 25 +nnoremap v6 :resize 30 +nnoremap v7 :resize 35 +nnoremap v8 :resize 40 +nnoremap v9 :resize 45 +nnoremap v0 :resize 50 + + +" BOL and EOL +nnoremap H ^ +vnoremap H ^ +nnoremap L $ +vnoremap L $ + + +" Search for visually selected text +vnoremap // y/"N + + +" trim trailing whitespace on save +" Are there any file type where I wouldn't want this? +autocmd BufWritePre *.{js,py,tpl,less,html,ex,exs,txt,hs,java,rs,ml} :%s/\s\+$//e + + +" Use .gitignore file to populate Ctrl-P +let g:ctrlp_user_command = ['.git', 'cd %s && git ls-files . -co --exclude-standard', 'find %s -type f'] + + +" Ignores dirs and files +let g:ctrlp_custom_ignore = { + \ 'dir': 'node_modules', + \ 'file': '\v\.(exe|dll|png|jpg|jpeg)$' +\} + + +" WIP: Run elixir tests on that line +" TODO: only register binding in *.exs? file extensions +nnoremap t :call ExTestToggle() + + +" Jumps from an Elixir module file to an Elixir test file. +fun! ExTestToggle() + if expand('%:e') == "ex" + + let test_file_name = expand('%:t:r') . "_test.exs" + let test_file_dir = substitute(expand('%:p:h'), "/lib/", "/test/", "") + let full_test_path = join([test_file_dir, test_file_name], "/") + + e `=full_test_path` + + elseif match(expand('%:t'), "_test.exs") != -1 + + let test_file_name = expand('%:t:r') + let offset_amt = strlen(test_file_name) - strlen("_test") + let module_file_name = strpart(test_file_name, 0, offset_amt) . ".ex" + let module_file_dir = substitute(expand('%:p:h'), "/test/", "/lib/", "") + let full_module_path = join([module_file_dir, module_file_name], "/") + + e `=full_module_path` + + endif +endfun + + +" Creates intermediate directories and file to match current buffer's filepath +fun! CreateNonExistingDirsAndFile() + ! echo "Creating directory..." && mkdir -p %:p:h && echo "Created directory." && echo "Creating file..." && touch %:t:p && echo "Created file." + + " Write the buffer to the recently created file. + w +endfun diff --git a/users/wpcarro/configs/.config/nvim/templates/boilerplate.c b/users/wpcarro/configs/.config/nvim/templates/boilerplate.c new file mode 100644 index 000000000..949743d72 --- /dev/null +++ b/users/wpcarro/configs/.config/nvim/templates/boilerplate.c @@ -0,0 +1,6 @@ +#include + +int main() { + printf("Hello, world!"); + return 0; +} diff --git a/users/wpcarro/configs/.config/nvim/templates/boilerplate.rs b/users/wpcarro/configs/.config/nvim/templates/boilerplate.rs new file mode 100644 index 000000000..c83adbc69 --- /dev/null +++ b/users/wpcarro/configs/.config/nvim/templates/boilerplate.rs @@ -0,0 +1,5 @@ +fn main() { + // The statements here will be executed when the compiled binary is called. + + println!("Hello, world!"); +} diff --git a/users/wpcarro/configs/.config/systemd/user/clipmenud.service b/users/wpcarro/configs/.config/systemd/user/clipmenud.service new file mode 100644 index 000000000..fac317f3f --- /dev/null +++ b/users/wpcarro/configs/.config/systemd/user/clipmenud.service @@ -0,0 +1,18 @@ +[Unit] +Description=Clipmenu daemon + +[Service] +ExecStart=clipmenud +Restart=always +RestartSec=500ms +Environment=DISPLAY=:0 + +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +ProtectControlGroups=yes +ProtectKernelTunables=yes +RestrictAddressFamilies= +RestrictRealtime=yes + +[Install] +WantedBy=default.target diff --git a/users/wpcarro/configs/.config/systemd/user/default.target.wants/clipmenud.service b/users/wpcarro/configs/.config/systemd/user/default.target.wants/clipmenud.service new file mode 120000 index 000000000..387f2023d --- /dev/null +++ b/users/wpcarro/configs/.config/systemd/user/default.target.wants/clipmenud.service @@ -0,0 +1 @@ +/usr/local/google/home/wpcarro/.config/systemd/user/clipmenud.service \ No newline at end of file diff --git a/users/wpcarro/configs/.config/systemd/user/lieer-google.service b/users/wpcarro/configs/.config/systemd/user/lieer-google.service new file mode 100644 index 000000000..2f79ed6cc --- /dev/null +++ b/users/wpcarro/configs/.config/systemd/user/lieer-google.service @@ -0,0 +1,7 @@ +[Unit] +Description=Lieer sync for account 'google' + +[Service] +Type=oneshot +ExecStart=/nix/store/n6c4pr4fyrsjfksspkapb7yqc6fzl166-corp-lieer/bin/gmi sync +WorkingDirectory=%h/mail/account.google diff --git a/users/wpcarro/configs/.config/systemd/user/lieer-google.timer b/users/wpcarro/configs/.config/systemd/user/lieer-google.timer new file mode 100644 index 000000000..a073da25e --- /dev/null +++ b/users/wpcarro/configs/.config/systemd/user/lieer-google.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Run lieer sync for account 'google' + +[Timer] +OnActiveSec=1 +OnUnitActiveSec=120 + +[Install] +WantedBy=timers.target diff --git a/users/wpcarro/configs/.config/systemd/user/timers.target.wants/lieer-google.timer b/users/wpcarro/configs/.config/systemd/user/timers.target.wants/lieer-google.timer new file mode 120000 index 000000000..e9f2cab3b --- /dev/null +++ b/users/wpcarro/configs/.config/systemd/user/timers.target.wants/lieer-google.timer @@ -0,0 +1 @@ +/usr/local/google/home/wpcarro/.config/systemd/user/lieer-google.timer \ No newline at end of file diff --git a/users/wpcarro/configs/.gnupg/crls.d/DIR.txt b/users/wpcarro/configs/.gnupg/crls.d/DIR.txt new file mode 100644 index 000000000..2a29a47b8 --- /dev/null +++ b/users/wpcarro/configs/.gnupg/crls.d/DIR.txt @@ -0,0 +1 @@ +v:1: diff --git a/users/wpcarro/configs/.gnupg/export.sh b/users/wpcarro/configs/.gnupg/export.sh new file mode 100755 index 000000000..571689773 --- /dev/null +++ b/users/wpcarro/configs/.gnupg/export.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e + +# Run this script to export all the information required to transport your GPG +# information. +# Usage: ./export.sh [directory] +# TODO: run this periodically as a job. + +destination="${1:-$(mktemp -d)}" + +if [ ! -d "$destination" ]; then + echo "$destination does not exist. Creating it..." + mkdir -p "$destination" +fi + +gpg --armor --export >"$destination/public.asc" +gpg --armor --export-secret-keys >"$destination/secret.asc" +gpg --armor --export-ownertrust >"$destination/ownertrust.txt" + +echo $(realpath "$destination") diff --git a/users/wpcarro/configs/.gnupg/exported/ownertrust.txt b/users/wpcarro/configs/.gnupg/exported/ownertrust.txt new file mode 100644 index 000000000..79b727914 --- /dev/null +++ b/users/wpcarro/configs/.gnupg/exported/ownertrust.txt @@ -0,0 +1,3 @@ +# List of assigned trustvalues, created Mon 29 Jul 2019 15:01:24 BST +# (Use "gpg --import-ownertrust" to restore them) +7E87921AAC9C514E9341C4F1C7A53CC58D3B1F8C:6: diff --git a/users/wpcarro/configs/.gnupg/exported/public.asc b/users/wpcarro/configs/.gnupg/exported/public.asc new file mode 100644 index 000000000..8b5547f4c --- /dev/null +++ b/users/wpcarro/configs/.gnupg/exported/public.asc @@ -0,0 +1,225 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFk52cEBEADW2uF8AjpGxbd/yrtCguVzl7fWCCo/vZYGTomoHy7K3ru7bQEN +upIBj1ElcsLGxbNLqdEqb17blTOUpaLLxWhEUw38rTpRyepBH0y2u5INDiw9GlpU +uXKnkvaAF2f7DJH24jQA2mLI5Jcgc2M0Kzmuh1Q1foAy3frORBnYlrd9TlSPU7Og +Jj0T20jtZIsIORov2TFC2cEpwa+9jHkNaBK2Bdg5c0SyI2r3TSJq+L7X8Vkf3Hmb +NEWJj286+ElcFP/FyVgRCtSJPjBg/MF0ucukm96cel5qYfK5RkMA/HCyv6xI8iNn +eZj8sJnozDY4rMxFwNkTxIjwH9cCTW0CR9FMsc1wlIe6Zx0ic8Fu7PZCS5MjM8cQ +LnruOunVnb0YodQ+cLde6FlKu7kNUlLJrH5NnuFxjWPxzC63u+/K6CcRV9ilWe5r +so/ImtNfGO1JiCvisYeOqlTYBKceQgjvu5tZtLJoGxH0UzoJARLCLRwyHN8dGqgp +STRd0Ze6LtzYLG0uuedyPNXDKci7GyrVdAmxVIo+eLA1a7n3YCcluGKZlM0IBWx8 +fTKJ16ASTXpK7Hqr3XSf5V7tUcwxiFFtxh5C7kXglyd4QI6Jk6Xp8HlPLvYXNSNj +VYRMHi/ueFI92jlt3kCodD26btgIEfD3e3JxKfHhOtwSoA2i1Hr43qdvtQARAQAB +tCNXaWxsaWFtIENhcnJvbGwgPHdwY2Fycm9AZ21haWwuY29tPokCVAQTAQgAPhYh +BH6HkhqsnFFOk0HE8celPMWNOx+MBQJZOdnBAhsDBQkJZgGABQsJCAcCBhUICQoL +AgQWAgMBAh4BAheAAAoJEMelPMWNOx+MGm8P/RYqv5mnneRbyJ6CgisYn2iIBQvz ++rmpdGDfkFqsd2YqDoGjzEJLVkan+I1oLnKSv5QJqPw1gG7fSv6X7Trov9J+Cma3 +h1bSn2BBiq8L9paWTILYmsrBe7kU9bQjNKFu0qjfvPqkGX6HXO6c81N00Qgie574 +MCByWgPtJTcbPLJodIxu6+aibwNBc3XInL/d0ZbXLs8Fc0+z2/dO7cmzAdE77d5Q +QaG9fGyztiYlZoUS9g3xT4ZulpPqs9zFa04fPvOXWVl+RQjZOYVYW/T8aVRnXohz +3y7tnxOWs8cmCFd91DDR099DZXstAesWllPsdSld18aMjeM2XrzuaWVDYktaraiY +RUdz6ZRPcaCpsIA2RHn++xEg+3q6QRz1J4bsbqEOKys+KlQO9uIgPMIkiMaLFoe5 +nu63XI4EMezrti86ETUxFPFL107P9KZ0gitjUXP0KSbnGQ7jt5FmuZgSAkbCSlis +Ulm8PZ/cmKj9OZysezDKkXFGtyskAELkpToIy48GtyEVIMk+CXcgNydUXDiLnWI8 +VwgmR1Q+hClLYMPvrk7OR6zK8txXsglJItCRUF5fmAn4Q7loh6i/BCfQpHdylO7G +nn7BOEJ0CJ8Hrr4Y785dtswAX8hWMIuzS4mxAHCjqkkfsOObBfLi+XpkZ6lDkfrQ +jAt2KuAjQR58dHDSiQEzBBABCAAdFiEEDxGpiYeei7v9weI2RO9bXoYcCacFAlmu +vnAACgkQRO9bXoYcCacQ4AgAmjDO+8Sd8d+cezwIjZgq1nPPb+/K0KTGsALe7jdF +MDOKwwPKd75mKbAVyJRu8CMEfgFW04YKbkeVp9bLeD2lpMYsIgpNYy5bU6DNCgi5 +QO501sTqeaWc/rlm7Ng5AlF8GIK6FagrPS31eUexxJ62VFozi3EiibKYepgeIUHR +3ukw6+PWBkvOYSQjZ0Uc08nci8UsewDQaQvuDABR+6WbLDYX6PuyUEzV7MPbyzME +QOvGuYpgcAq5gGB6NNe9zFQK0xAQob1UhDlaa1p8VSZyH/RLnyYCdlq0Bmf95PNq +eE8YkmqFCPKNoWwzOa6pJOk/Y2mXhJm/eD4Avmlo122LN4kCMwQQAQgAHRYhBBP5 +Ly1Y78tmaShOtNywW0z4mvxmBQJZvs4vAAoJENywW0z4mvxmIAoQAMM6QLexK9fK +88EZxYC6x+qYkb3rzjaGyO3dzGhfRQTFJ8HtFrWTR5s/m+1ACKFnbf1xo7AWbsYq +jVIxXsqUt9a4jserpaczlzDQLojFCKSGFmfCVV40FwQHL3W+C40xLHLOq93bpHLD +knp38daRzSryW11ev9y0J3to0qX03WpgFKKwT3fQMT5+V4wZNuzOFmWGaPUsuCQh +SQj5VU+p9Q4soIIzu2gaal4vW3/qZgIlkAmkg0FV5iW7mwScWPcF/kPlkHFMj9pG +aTNVgegxtUQBJEXx+VF0vDiOtRnBjE2woVLq1FWGkn7feX4Jvnajqpf6A8dKeNcv +Egfm00HHhhR5f7LTOYTSXWCqhCmIBKGpYlZo/PDGHdlVoRSR5+qN/1kyP7WZg35u ++7XR0paCTR71RwO+oHZHiv97u6P/iPvVWE7aqCJe6kBW6Q8hB4zjLfH6AbjcIN/9 +Vy0k3ALBfNxatkJZAyl/PQpgSpfpAkbk5upBPvOKVGCWsYA5sYRH4MMiwrOuSgCh +nibnUT4jJjgU7hK7iOTJB1mbNEvyMSksgxtbdA4XWee4iv5qS3ZCX+1RUspZk3IS +8NePnaycg+OlI5gMbSEVEmLCat2V3l5KNZRFWpbmNTCo+Mi4t1i8kgJAHRtn0r6t +sIS9beklhQz1p7KZAphYWjl6kO9p80cnuQINBFk52cEBEAC8e3b6SN4t5I/RRmRt +/YbPFyC81yElaPzBM+OFsbRDr9MrtfeDUp/wgcihQIw01HUDlm4F2WjbGwth/8Zs +tEML3CFwtv4V+sYhKqfg+sS5YqzFrFWfZYod8ppFKNMaw9Pcjl70td2egcBDt1SR +51ni4SdhyMt/KOm4mym/Lf4UNyzlYwykbjtb3nsmvxYI/uVdceDv+7vZoW1rapSw +Zj4ZS8+jhgHrO2p5B5TCHdsDJEYQ2SOYeIm8tfqb4oQlTWwjG0frl5eOp/+9HfK1 +q7R049FMAzmd6fbm0jpzxDveDe58qWWq2aj+7HwTZhmvr9l7uFyR+3TL6s9hEALl +B3RGpOy5hGmvwLIAi4qylZuRJzW0tMveDcaDEdMhtyEKF9DYpk1Ug/01uG+PzK8e +TiFyTCN6OAawWIe7pIxRhlk4+CIqRPEprMTfDJxKEUS7RnUYZ76E94FSV9nEqIJj +UWFLq3aTMqSiSN5LAgT6AJKHrR2+cJVHM1cEILvCIugeuw1GUC+Bxs1qaxIpp4SS +xi9Givx8PkKuCukp1J4B8Alx0ZpRELsBEuhZdEN2LP+Hpz7uyD875r8BvEJ+hU20 +Qfnj2wHmKVF+6jBPwrpzZ/gbXuQzfstmrSJCY6p6izfcQSJmbSNpaTsrOvgmaUev +Wuss4bjuG4loSRwFb/7fsPsX9wARAQABiQI8BBgBCAAmFiEEfoeSGqycUU6TQcTx +x6U8xY07H4wFAlk52cECGwwFCQlmAYAACgkQx6U8xY07H4xvTg/+MbWyGFmLe9b8 +AMJtqwX+3EyP44Mo2CeafvmbPSqxoXh1NdOezlEESZU9fHMDY50IA2hpariO/Le3 +Ck8py5NAznCc4avS+gnahcPyhvUaMCskmN4UaRsohMvxKrdAGyRfXZcQqE5Tu6zM +6TxycQkT00qe+PTSQnV2dvGXE4iBFV5kj3NHV7RBC+7sDZ7cYuLHrw1gOaMWeCcF +oQ0l+DW3CNh3klFds/PIfPALjV25+niwOYcenxqp8GVjooWj7xkASkFyZAqusFTa ++/XY2y7+jvdSmm36gfWiPXWdpPiesekPK1NqPGdAtyv1/EKJd+7cYCbkSH6qPJpb +FnpfK/ItVm39OIe7OVUuZYd4lGeFvKK/nDqSQ+9STVar2+n8Wths4C8KJbLdZxGE +QYWKw5aL09tpQRy/skReAt9hVDq3qflODyuWPqS/oDbSGERA2NndkV3LIkU+ZCXI +xsin4IP8XzC8yJjv11PAzM8wmhlXWOdKDIZXMV/2wO+cyM/t9WtfeXUOuk2NyWO4 +nQ+gD3cDPTJS+t8ReKb/bEXSeHiRfgiYuZXcwT5vGmx4HiYgJ7d9i+8Ikcew75zw +EvR+OLXYVICsI9PiQPzqVBfp+2u5saEvmWORrNLLUBQt24R2CF4Y63pLumsCB3gv +n+cUG+sEsct2sSKhQEEU7Yra9tppHlO5Ag0EWT7OXAEQANUBV3/GFcAtsM9XiSGL +GuWs+S8np+A7WRIISsR5BU22u9XqF7P8/5o/ZJIvoNu9EwTkFZYP9pAxx7F+I/62 +x8YbXCU5byiOG0X/RKRafW1j9zJdZHK2jdga2pRUiCexpk43knTMyYUxyNlOFM+r +UKJlMErTaN8PJldD9f7qq+assxFN+rLW+tWwxtcL9WxdCIBBMKE7ldyaIRzKNMbZ +b+mckdP412Ht05sn2BijgHj/co07M0zw+MLWNc9c50wI+/CYBZXerDp7TZoB2HxD +JH9FqQ+ypjZQkNArieMj8IB6o8nZRbOqs+FO3LJf4A2WuHSLb187LpTLGoL/WaGG +YK5ZE/0DuR7lSZmVYt2qaKqzDUJbbETLNJlhEykKI0f4bkhjI9UPjxlFgarDuFOe +V7KkZXJoapda6lwy+W8GDAcrtMasMIgN5lMQdYJllIOJVs0wTJyMbLFN1hcr8oGc +09tLEPr0FO5lxygMHqcNiia1SO34IJF6HaRHZQeX6Hv2M+FHWpZ3fcu9dkY9i9JK +RM0W3x0OLFBNfCAvBVFxat9xmYYJuRqomPAFIlt3hik/Dl3dWOkPLdVoE97T/r1/ +5DDNBpaO/g7XU24bH6ja9/WO8T9g0L4nTLCtdKSaGFxwT0No7jmgTJxJQan7hHDZ ++0CHj8jCUsMNSK587HTWiZFZABEBAAGJBHIEGAEIACYWIQR+h5IarJxRTpNBxPHH +pTzFjTsfjAUCWT7OXAIbAgUJCWYBgAJACRDHpTzFjTsfjMF0IAQZAQgAHRYhBOpX +PxAcncyJjCSg9h7cm2rmkT65BQJZPs5cAAoJEB7cm2rmkT65H2sQAMlGuoA6pKlA +W9L+Mdn3aHaGHzxiLU0mxJZHLPxLru9YNkEF2uDiSzHRMSSbJCujF8O2Z8jg08f2 ++r/ZVHK1wWd/J7zikh/1pMVj9KVNG0JdyqiHuQ02i5vWdBg6lZZku4uUvU616Ynh +PMDVoEQ6QXQ24BhrSBH9B1tgSnIRc9EHzzW5lTF3+qttA1tJETJODzEZGmSlBene +kUxBZVuH7daEa1pRPGNVSa4TmCTkxgYZUIdnIDC7CeDREldAftCvEll37Ewy8QTi +goAPYYZQd9jJ7ywq1qWFiTt6n6IYvjfVm2ttO/3PBtla5FdCW3U0MZyxOiIIpHjh +IgVFWknvOEsKQhaN4rbq7YSMLLZW5Y0ukHk8c3OsRpO5Clc/yl0hHURGtjhvHgSg +kL8z80WhnP+XiMa/vaWIsats1/LGc+uvstmohI4np9+jF/6Byk9Y85ki1ilQszVP +/JEIj2IvH6/OsjcXlgAs8In7EBeixlERLneK9F0D+rb99m21rhkXmy/EQ75yeSV/ +z7sy9suK3SuULqYSuTKuw+mbAY3KOF3JLjQdW1NXCZhqRaxYF+5nMq1f6VDwGcpO +pPSF+9fY6/R5/cyEk8LOY4kS9O9rqc4PIi1ieYoyxezTBdX1aWpZEsAzVDWuJRCd +pwaKM8YF7ofZxEo5QAnfOO/gc1opPVIUMzAP/2md27adc9Qn0AkWf4Gbr5HSbArq +urGjqUa8HCUcishzW/TrWbDtuy0ZplvWv4z1KgqoAAr+U2rZwck8mk02UCmjpaDL +KliTiBAuRei6wgW0y7rLE0S1q9wjoJZemhVrI5C0hu7jIPzl1r/ZRNEqcKtsl6UG ++2zlo0loGiLt+G+p5KpcwuscYGEU0Ekasu+68sual5UUdRXpu8J9ePnqSIZUHanj +mrARW5iv17NGG3ZtGeX+rDZ2G23Ymnc/IOK1Qeyz07GE3OcxaikTk4EvNDJcl33E +uI7Dcauz+1msBxGwkib/BUx686ZI8e3Qa9cxzmzGBAwJGqtuOv0BrKbmT6dJ1FG2 +XtRCKsYDGttvoEM8fnhAeVXLIEidiMg3ri8cE/uhIKQlTCoel/hr6yM0BztxQRIk +PIkFDGSpOq6pknv0KgAxhymJlHmC002e3FAl+B+q71FUthwjs7h110CrI6yZTwbC +La9FZEODAFWkWLghV3iLP0D8HD+rsBxDttpJOC0lndsONIMkb2Xf4ue8pceUehEN +zf+mkS6B5ilfHOhrcY3vkfV/cuF2Zv1kBpjayCbanweeyEIok720eP4RoTu8NJGZ +gD0qLo2iJBW65FRr557CWET+X22k2vDO92PeB6MXdZ+PGNgPWw+SZfY5nUN6hsbm +73dNlPTad6zfMtHRuQINBFk+zuMBEADd0yUpELWiFKezO/GLaqBs5iI5fRvO97pk +5yhROIjaM4xz2tmZMvenO9AdVSchgh6w1CCNMvhbE9MkEakh8qN5hWl0XeN+UarF +XvIx1ARfzmI+Xwz40wNRNHkGMwZipvHQ0oFW2NI+qQaxu1QzX7eGIF1uQPhyw5wg +dQeO3fbAKR7G1YNOiBM5KyEPSFj29fSyPVjhqM5orHyrD3rtHir+978hA0W4yFY3 +0lY7OqaHs2crU4txy30bc2oYL93J5uMDeXmDg+K7NEGeih1vJnh3lF73yh3i6d+t +MxrMyzkhLf8GBD+38QqJ4npkcd4E66g0kB3Q1EYfh4R2eMkYCCcTuRuQVrtIyC81 +r490Kx3WFB2ioIOARk/0NUrnqO0tze4KuLKc4wVpKFCeRkBpN5ZxoF+NJ2DLodg8 +w5G5xkMluLpcUWU+D/LEf52Nu8JBro4BVOv8h+D3MwC8icaQaL4xTVqCBtOvhA3/ +0gxrEJ98g5jpR/Puspa/zQVpY2MP632m6Ctfw6mdHtcq3PKX2sJIF8UKLGhJsAYg +9bUSD4ka+IV1BLXY71b2DFZoTA9WaprG4GDTV4x3PcERq1/LSUBIKl9kHw9DvGzW +uBcQQOwUR564ghGPt4Lxq8fc5G03h2Oou6LZx1lfYHuwQs8iJtiX6EtkVhZxxQX5 +TClQMdVgEwARAQABiQI8BBgBCAAmFiEEfoeSGqycUU6TQcTxx6U8xY07H4wFAlk+ +zuMCGwwFCQlmAYAACgkQx6U8xY07H4yNohAApKlliONc+s6PMtwAOJ3j3NzOCPDy +MOiA24kKHMg4yCUiJDJ+xX2tQs6Jf99IcCIF625nnsUqyRDgdHyDeu4ZTneo1aFB +YMf4fgxqUkEiV7VNxvw2idDfW2Wy0fmyGCdS8UOw9UOjSMURNjfvY/pQlFNG+cWx +ZUfrU2HgXzdAchTlYQnpPwaDaMQE7xsV/Q+VVtWNe9gAuycrGNgPhh4zNAmjqiGN +a+YS0vW4v+TSaA7Y/jMTEJYEz9+60dYy4I64Wv1NWODT03KExQoINrROLwhn/wD8 +AZhyJKBNuAbSZpMXNMD+2QKtqeNxE7HbTQY7Bqx5feBvDkDgr7ox+KyzR3NuXOHQ +CSbmSEQPN3miiGglHGASctp3Fd93PhXzVtiiRAnqfBw7zGDSgdpaNC8z9DAG6iUY +ZtcNz5UoiCHSOqE1vxV38poWDtZLkKuQRXvXy/uNyPcPx2efaDNf/FxH3gM6L7+6 +gfJ9vDMKUI9Xa9A5u3BMR/1Xiehx/GL7kZ16fZDWhJH1iCUcPwu/wSPDCmzGaX3B +O/FT+H1m/Fql8oKWOy82K1zBVMx7cx+b+3/Qlkbx3wGPGtNLPH1m5QFKoPV7z2zP +tM9VziUSLPPlIEbsT3I4lXYdhFsHbGBk++ZbY9kRUTXkNRciqX2NFcFtNSo9RH8F +KOmVBBI+ZQ28HDGJAh8EKAEIAAkFAlm/5kACHQMACgkQx6U8xY07H4yS/xAAiHYW +T+wgYgFR0e0DYNOlz3KeYZwhORc5/ED07qCxUMFkChpBnXbKLzGViiKK3H9FyaFy +fOhuIqb0GgXX4TTYdHShvceBtMfTfeYMLXQC+WaJgIydbjRK978mDBgIDs96ylEj +ErtgP3J/GXTk616nv9VYYjGGNKQVJNxDGCRzfZks3m/gWH/whbctFQXaBOshstra +nGpoR6EEZERpDkDMdLqd1JEhCPK8YUSPwT0LKm2yQpeR3ly5phZiJC7uZVmq518P +f8UoHYhtb6P18kJeMVbrNpEzDTdGCZ6eKC3B+1tfoft8MXF3fEINFzZKqAXKqsHD +sQtVWPshg49J8HNpc0NbQi90+8ph4oVrWDHoDulhGg9xUTlY1fXUye1uDXhVn8gL +rAeLqz6WP2i7jPnNCJgTXw5+e2kAye0rCvKH0pw8a1Aq2iaxvxr0L1MzAgtKaTh/ +AU3K5j8r7YRUdOMUHMGS5CwwdhwNkABM2Sm7FmlZL/BNwmgxekhJSivLL3M6qPY3 +LjcxxJBfe4gk9RRX9/YCgSkKTwvx1Ko9368G4WcxYOSTP3eVol6o0yBqd0rV/P+l +CCgiAZ/ZoVOvi5jmTy2I9flafPzGp51EdH+RS/rGwW1feP5JMg+NULwc1y4kTru6 +pkHTUu3Ol+M2616HU32p8XJi4mDV5qMRWmLn+UCZAQ0EVKyAeQEIALyGS95q8aCp +8rjM35kpabNOhr9hAcdq0DrxwjOWZd5u569X1sS81VjPqoj1jpA+/GgheWeYrNxm +RbMT1fdtd22W5yiNd5TNXF+RMhZYvnT4Mxm3NNggZoriHsnrG4XbtLZZmMTXwF/6 +a/CsaCXYHp4J3YvYnDc/B0fssj37OXQH0SjpBQnU7U1m7mvLXfm7Mh+zi7VTSz45 +WcFyr7Lg1HRN6OzDtbBjn8kuWWMzYIlg+EUZPuHLHoCkjhN6g7AM5eDhQqXvzOcy +lSIk/TIPy7n8EeKrrgfijGQOJF6c6d5n0Hq+6lejT6uL3iHUOKtv8PYGr8vF01ao +vP/EOVTkYQEAEQEAAbQrR3JpZmZpbiBTbWl0aCA8Z3NtaXRoQHNlY3VyaXR5c2Nv +cmVjYXJkLmlvPokBNwQTAQgAIQUCWCuGZQIbAwULCQgHAgYVCAkKCwIEFgIDAQIe +AQIXgAAKCRBE71tehhwJp2lCCACiTsLoGbq44A11+k24oWItbJTrg5pISwKUwfwt +hvik0oQPWfQoz/sr0w/Ie0rUnCuSyOVUXuJZSgzFOjEcwmw1dDv0hsanyt+NZ3SC +r/hSasAOMIeXS7+hyL894E6NKIGDi25+Yhpj1AFneCu9cOoxlEXqynVaiBJbpHIw +atwB5i7ZvUz+krTBjf6wwgLzBi1EHw7IJYhgS1Ye9A/+h+iur7d/4/C6cC31IgBd +r8d8iNbMhqyk66+fhdZ5Vd2QO4DUq4CUgFoakO9X383Jf8azR0zXIPPphJ2QpQzD +sfriUT0J18bP546tknwOsNYlt1XPYwlLvXKljXr1YkRyTdPTtCdHcmlmZmluIFNt +aXRoIDx3aWxkZ3JpZmZpbjQ1QGdtYWlsLmNvbT6JATcEEwEIACEFAlSsgHkCGwMF +CwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQRO9bXoYcCaed+Qf+JSDZ3odMwlnr +bb2kwslduAt9VhRm+dfdIm25nAgxJUxIju8uIgE9v+8dRdGFwrV8pKBYYOCMi8MF +NYuu9zS66wXS4opd/DeYDj2yaN0wBYEfeXMCwVLVDHU7AHrsxQWRSxbcUOi2Mm2s +ig70ZSq2iNicX2f6eUSr/4CjocTP6jOqcHd6Di4odEy/hK6ukCCW8ia1Uujh7JYC +U7quHnuE1N184W2Jf6hUieFC2kE+Nmhix0LsYYe6c1InembHRZ85BpOsWWuE9cS6 +IuVO/jbNZcgS7NkuCHkG7CubPnSZX/EDwmyr9Pd57tr9BANuDNvTGgcbaXhJj5nl +Ix/usDsrRLQxR3JpZmZpbiBTbWl0aCAoS2V5YmFzZSkgPGdsaXR0ZXJzaGFya0Br +ZXliYXNlLmlvPokBNwQTAQgAIQUCWCuHlgIbAwULCQgHAgYVCAkKCwIEFgIDAQIe +AQIXgAAKCRBE71tehhwJpxWTB/0ehZJ1Bjkf7AvtWYn7PEwr1y9aAWHLhAxNNXOE +M5IhXjnpL5o3Pic8DonUrzDVxRsNxaGU8jvAvbQpWgtQXJFi0qgDxS6b1hf5CSlS +kcjqtkcMMqyi7XAydSyCXr5s0sZ2ZBn0tri0AKN7JW4Wd0aXJrP/RmmXNeTTARI/ +LGy5Em/PBFogDPTHRWwJQ5uCaddwev3pcOzNvrSvR0m1JXG+ZtP/Z+c4QQA3YGdT +TSxanK2w9NXTQVToJKO8Lig3ivYNgpbscE0ywrbXVfu3pzB1+9uTa3zd9MmQ0QL9 +mX3RiJeExNE+Vxj5jG+kE8GhcRxXKefXkg+UweaYfkcX8vEEuQENBFSsgHkBCADI +E/6vQg1OW9aGffzp3atrHtCjEHU6ZONE6unlez4CGHZXIZYTAbA0Nmgd3d3JA7wd +d0p48whI/tREFHlBD4lxQBN3wrpmDFVq0OiSLuMSAZaTXrX5ctY4CiHJVOIJUK16 +6zsoQFqvTBW7hYTsmFml1frOZrnyeYD9Hyj1Kkk1kaUkf+JrtnZzcftqD0hFzYHe +645YsLS2ub/ZoXrlV1hznDdIH64TYwlvabvBcZR6Exn6+hByMSbem1nNqB4PN2GV +/dO2OrkolThctGaxVoChDoauA+vfUQRWbpxzMJQHAJ3/PtKMKyMjv0+TTSIO1zsp +i2mayI7XUyXLu5fcTfQxABEBAAGJAR8EGAEIAAkFAlSsgHkCGwwACgkQRO9bXoYc +Cae2+Qf/QWJ+sVhFHNHUjPWSL1o+dSUMIv6qseCGyojGLZxAl9z6IKUng638XMrV +kgAy1aoy91N+HY0IPg45huTvU36uFoD2Hr9dd+ZVftO38jfviiowqu+iPt16sfZq +f9VUTDTJpsLzoxiwq+x5FbJYt2iqDqK30JyQD2EMn5Li0qtR1ohunxR2CE5byNRA +1ymk1BKMDb0tDHl87fCY5+bHZBrG0svqyDsxxK0T4bqRl0gSUfhVA67xcL1C7wG1 +MmtNZM+Ks9AepFlxmRDJnX0XNdaw7P6QtK4igLw5hSiFpVYmdfEyL9W0yn4No4Pj +rJnQvrCvIJqJ+1ANxY9H9ArCl3iF/5kCDQRZvrPlARAA1DjXoVu6jU9Rfojm7iFu +XJm2Suq7W3v4HjsycExn3ZBh2Lh5Jc7EdncPbP3UWnNBI5MlerHS5VMfC1OFzG/Z +IRXZyWIVOu1ajRH39i/8pIxOfcCQ1Y4msN0QntL79Z1qAtOdUGqppViiywgTA7XC +yGU8lvVEx7TlgXdmviRSI2Mm2McbeD/YLdTgqD8+8J00sIW5DUk1n/gUkyU2z4mk +4rwvGLJR2bGv1KIndZzfg7c+fNd2UsXjcJV8+eMRJCjE+xlrviBnJHnrNj75Ps9v +xX4WDX2PQZycS4NEictsFOmWmyiAHWcW8ZOrqsrDbd2QGUxjKrh6kdrZvVhCogsO +StwdVm39TfBykLt3K5jdhU5QK4AyNBbVoikoBtZFyVg2G6e0vbVAYF5NDoy+uDyM +jK9cHeJfMHRPCW+rBkaNh4+i0uj97SML9F1G1s3+dlPSNIofvJvZ69VxJi5w0OCT +vwdgmR/na7DQTBikZK+F0hoZRJAmKgxh0yUMzExYUq9rvdgNeEyIWs3nh6GVPs15 +iZfcRITiGFZsi/BWeSH/ce96qYG+c5UkN1QOvFqMZdneF/uUfUI/qOc3KGmhjW+f +lcO/qqtROO3UisckjvUZL1/80YQISMn96iJpG8mOUrDHiHndSMwErMyyxz3XXYzQ +ys7W2oihWL5iKU+SHmisNA0AEQEAAbQqSnVzdGluIERlTWFyaXMgKGh0dHA0MDIp +IDxqdXN0aW5AZGVtYXIuaXM+iQI+BBMBAgAoBQJZvrPlAhsDBQkDwmcABgsJCAcD +AgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDcsFtM+Jr8ZiuBD/9dOkCFWDZWAj1LKBc6 +nKci16H8tazePpvjYeZFxJw2w5NEGgwg1iGsdhKm60EOA8Okh8cEmmfq+AriFPEH +nAbnSePGNeXTRFy7njaApxnRQGaVTV+++B/J/zQTA+2iJXh4gWR2/ip6gmyAGQJK +u63jA/fwoeWqcQDI1FnHqpGEHb6BLeeyRA7iXd3TYTOYpuFJGx57yhZflFssbmwY +3MW3NyWdOYXkWiH1OfujYHuh5du5txiEMvN78q5F18byIaLSkoa3eOM6osGOn8kj +X3iJEAOk+HzMONQd2O59OWmozzyxHicr4rv7LIOeAvL9gi+gpEflT65/AJbgLa5N +JLcvQwAD/iRRv9fs7CSsOlwLyZEVGy0huZ2iyxSguBwkDsHd6yFAr90F8eehV4z6 +DlW3g5UREVQEcUIKW4FEg+E0XMe7tILOcqTzhsMrd3PwMmC/RDPoyOOhJLCLFAg9 +hN+xaFEr4cDUPT1PwudHqQ9u9uqyeH47O3Qi0KJ2IsrWgcjTy3z0setCZDh3APlv +Y9o+Go4ykvMNV/iHwnui/CS/sIX64VKrBq5L+0cq3PnbJfeqxi/Q7CRBko4Zf2h1 +A5SkSM3lwSuLk+zLNu+erS13EjwfainK4eOgFism7lN5CD4Z7VrKxtOTKjozidG0 +N3Ez7edUYQ28NGWJBIMEb6qn/7kCDQRZvrPlARAAoWI61RD8wjDINkiXAtX0jcoG +dvO9oMXvVFWqsGEGivqciifdA5VvB/9jK0YfFQbLvQtkfvcqITuGflBExCK47CDg +lv4AxI0xNkj1jKwgvm/tU6y+Oe1mrw+b64Z/V5naptNnIU4VgVSNsWvZkH2EKxgq +6k+fAoCCwxlctw2JMmbnmUNOiu2miwoiq/Agl8Jfd4xSrAGZn77ZHM+XNLgabKiJ +782E3alCFOXbIftOXIcxgOQWbiiPEUjzCJ5llMdjVnOkn7uP+ZXm3/h7IsrC9/GP +DqSsebGPbNuxgNrDj9HigYPNK6jjZ95bLImaADfd2h/OXA9FYz+HwJ2kBZRNGtDj +FxsmLvqNolu8WZKSjiw7SNK0Ya+55y8KU2iO/G3T5ilAB6nRPliP9aE6IIEzNWgK +9nNM92S34CZQMhOnVjRqH4WEi9i3j/rlSAX4mJLbe2pi6cueSBc53qisBs6H8p5Z +hqPQJfUlVeRxF4ZNKF7wt6dhQcEbi3/IxoABBizIt5DSBybOMLOAB65A5GQkPlJ7 +VyHhzlIoS5RqzwOqg6TCQ4UUtifQqtFXuwVHlHAPhi56U10IiCWJd4hy635Eei6C +WDmGr4+eXTK14h93f79JxIqqve5y7cZgcQ+dQPSBVl19FZnvQUA4/5E8UPI1X3cQ +o8I1542PpL74CcXBZ1kAEQEAAYkCJQQYAQIADwUCWb6z5QIbDAUJA8JnAAAKCRDc +sFtM+Jr8ZqSQEACFW1xrxu8mmXGXpLlXpGx9CFWBfJSFtWBYNjbLZ8Rcdu8FweBt +HVIAmdwYbBHYgT/xQd9Gxg+Z+JmnaAZFi88pN5Pmh2dy53nysLsjYZS8G7p2lKdu +alXrM90PxGpwugNNPVEr//+Bb6ahQgnJQLDY6wz3DuA3L1vk+sBN+00iuGbaW992 +kPRcz2KSDXY0jR3lh3938qBXJR6jbYN2YxHMhfCeK8y9hpNSP5UVUYlLeEjyEIT7 +HgbMwsX8WX8OvL+uacwSzwC1JE8Vn98pIEQgMveZn9ylwuJZp1zv5eSulDsDRWA8 +S8Agjb/fjdQGsck4REiahw3DIPqcIvUFr3yDybB8dTLp509UqK+HLw/bf8QMmpc8 +YazIquk0/HVm0tdijDCgAIw+Dh8LEP1gmCVxynlrHs9ItCjlipuTao8LopjwOiGE +9LnYy19ESF7kUbtyFenmp+FX0WAvlUfhrSfeeM+vR1yD8dJSa1XDI/WafkxBQRuV +sd3cfQIxDn7JGlhwytRkxl7oabxHokc3wCSzu5Sb4ok6A91HPbSNqJ2nKSkbAwQE +u6d5vx6SSO02stvyHPUFLM7zTTNa2B5Vrz9e7eItKEm6taXqzx1e7A8w+kOlP/0p +4oLlBDWgklpqVZcPWtFDsldyNZlxwo5xw9czlTZ+hVuaHdSqwP2NNQegYw== +=5XsB +-----END PGP PUBLIC KEY BLOCK----- diff --git a/users/wpcarro/configs/.gnupg/import.sh b/users/wpcarro/configs/.gnupg/import.sh new file mode 100755 index 000000000..e698aa3d2 --- /dev/null +++ b/users/wpcarro/configs/.gnupg/import.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +# Run this script to import all of the information exported by `export.sh`. +# Usage: ./import.sh path/to/directory + +gpg --import "$1/public.asc" +gpg --import "$1/secret.asc" +gpg --import-ownertrust "$1/ownertrust.txt" + +# Run this at the end to output some verification +gpg --list-keys diff --git a/users/wpcarro/configs/.gnupg/pubring.kbx b/users/wpcarro/configs/.gnupg/pubring.kbx new file mode 100644 index 000000000..208fad71b Binary files /dev/null and b/users/wpcarro/configs/.gnupg/pubring.kbx differ diff --git a/users/wpcarro/configs/.gnupg/trustdb.gpg b/users/wpcarro/configs/.gnupg/trustdb.gpg new file mode 100644 index 000000000..8781b2ad9 Binary files /dev/null and b/users/wpcarro/configs/.gnupg/trustdb.gpg differ diff --git a/users/wpcarro/configs/.sqliterc b/users/wpcarro/configs/.sqliterc new file mode 100644 index 000000000..7e8b3e3fb --- /dev/null +++ b/users/wpcarro/configs/.sqliterc @@ -0,0 +1,2 @@ +.mode column +.headers on \ No newline at end of file diff --git a/users/wpcarro/configs/.xsecurelockrc b/users/wpcarro/configs/.xsecurelockrc new file mode 100644 index 000000000..101495c3e --- /dev/null +++ b/users/wpcarro/configs/.xsecurelockrc @@ -0,0 +1,5 @@ +# Replace the gLinux penguin with a custom image. +XSECURELOCK_LOGO_IMAGE=~/.local/share/static/pickle-rick.jpg + +# Turn this off on laptop (not on desktop). +XSECURELOCK_BLANK_DPMS_STATE=on diff --git a/users/wpcarro/configs/install b/users/wpcarro/configs/install new file mode 100755 index 000000000..ec9403682 --- /dev/null +++ b/users/wpcarro/configs/install @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +configs="$BRIEFCASE/configs" + +(cd "$configs" && stow --target="$HOME" .) diff --git a/users/wpcarro/configs/uninstall b/users/wpcarro/configs/uninstall new file mode 100755 index 000000000..e082d75ce --- /dev/null +++ b/users/wpcarro/configs/uninstall @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +configs="$BRIEFCASE/configs" + +(cd "$configs" && stow --delete --target="$HOME" .) diff --git a/users/wpcarro/default.nix b/users/wpcarro/default.nix new file mode 100644 index 000000000..ab0afe9bf --- /dev/null +++ b/users/wpcarro/default.nix @@ -0,0 +1,4 @@ +_: { + # temporarily commented out while briefcase is being integrated in + # depot. +} diff --git a/users/wpcarro/emacs/.emacs.d/init.el b/users/wpcarro/emacs/.emacs.d/init.el new file mode 100644 index 000000000..a54ab1835 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/init.el @@ -0,0 +1,21 @@ +;; load order is intentional +(require 'wpc-package) +(require 'wpc-misc) +(require 'ssh) +(require 'keyboard) +(require 'irc) +(require 'email) +(require 'keybindings) +(require 'window-manager) +(require 'wpc-ui) +(require 'wpc-dired) +(require 'wpc-org) +(require 'wpc-company) +(require 'wpc-shell) +(require 'wpc-lisp) +(require 'wpc-haskell) +(require 'wpc-elixir) +(require 'wpc-nix) +(require 'wpc-rust) +(require 'wpc-clojure) +(require 'wpc-prolog) diff --git a/users/wpcarro/emacs/.emacs.d/opam-user-setup.el b/users/wpcarro/emacs/.emacs.d/opam-user-setup.el new file mode 100644 index 000000000..a23addefa --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/opam-user-setup.el @@ -0,0 +1,145 @@ +;; ## added by OPAM user-setup for emacs / base ## cfd3c9b7837c85cffd0c59de521990f0 ## you can edit, but keep this line +(provide 'opam-user-setup) + +;; Base configuration for OPAM + +(defun opam-shell-command-to-string (command) + "Similar to shell-command-to-string, but returns nil unless the process + returned 0, and ignores stderr (shell-command-to-string ignores return value)" + (let* ((return-value 0) + (return-string + (with-output-to-string + (setq return-value + (with-current-buffer standard-output + (process-file shell-file-name nil '(t nil) nil + shell-command-switch command)))))) + (if (= return-value 0) return-string nil))) + +(defun opam-update-env (switch) + "Update the environment to follow current OPAM switch configuration" + (interactive + (list + (let ((default + (car (split-string (opam-shell-command-to-string "opam switch show --safe"))))) + (completing-read + (concat "opam switch (" default "): ") + (split-string (opam-shell-command-to-string "opam switch list -s --safe") "\n") + nil t nil nil default)))) + (let* ((switch-arg (if (= 0 (length switch)) "" (concat "--switch " switch))) + (command (concat "opam config env --safe --sexp " switch-arg)) + (env (opam-shell-command-to-string command))) + (when (and env (not (string= env ""))) + (dolist (var (car (read-from-string env))) + (setenv (car var) (cadr var)) + (when (string= (car var) "PATH") + (setq exec-path (split-string (cadr var) path-separator))))))) + +(opam-update-env nil) + +(defvar opam-share + (let ((reply (opam-shell-command-to-string "opam config var share --safe"))) + (when reply (substring reply 0 -1)))) + +(add-to-list 'load-path (concat opam-share "/emacs/site-lisp")) +;; OPAM-installed tools automated detection and initialisation + +(defun opam-setup-tuareg () + (add-to-list 'load-path (concat opam-share "/tuareg") t) + (load "tuareg-site-file")) + +(defun opam-setup-add-ocaml-hook (h) + (add-hook 'tuareg-mode-hook h t) + (add-hook 'caml-mode-hook h t)) + +(defun opam-setup-complete () + (if (require 'company nil t) + (opam-setup-add-ocaml-hook + (lambda () + (company-mode) + (defalias 'auto-complete 'company-complete))) + (require 'auto-complete nil t))) + +(defun opam-setup-ocp-indent () + (opam-setup-complete) + (autoload 'ocp-setup-indent "ocp-indent" "Improved indentation for Tuareg mode") + (autoload 'ocp-indent-caml-mode-setup "ocp-indent" "Improved indentation for Caml mode") + (add-hook 'tuareg-mode-hook 'ocp-setup-indent t) + (add-hook 'caml-mode-hook 'ocp-indent-caml-mode-setup t)) + +(defun opam-setup-ocp-index () + (autoload 'ocp-index-mode "ocp-index" "OCaml code browsing, documentation and completion based on build artefacts") + (opam-setup-add-ocaml-hook 'ocp-index-mode)) + +(defun opam-setup-merlin () + (opam-setup-complete) + (require 'merlin) + (opam-setup-add-ocaml-hook 'merlin-mode) + + (defcustom ocp-index-use-auto-complete nil + "Use auto-complete with ocp-index (disabled by default by opam-user-setup because merlin is in use)" + :group 'ocp_index) + (defcustom merlin-ac-setup 'easy + "Use auto-complete with merlin (enabled by default by opam-user-setup)" + :group 'merlin-ac) + + ;; So you can do it on a mac, where `C-` and `C-` are used + ;; by spaces. + (define-key merlin-mode-map + (kbd "C-c ") 'merlin-type-enclosing-go-up) + (define-key merlin-mode-map + (kbd "C-c ") 'merlin-type-enclosing-go-down) + (set-face-background 'merlin-type-face "skyblue")) + +(defun opam-setup-utop () + (autoload 'utop "utop" "Toplevel for OCaml" t) + (autoload 'utop-minor-mode "utop" "Minor mode for utop" t) + (add-hook 'tuareg-mode-hook 'utop-minor-mode)) + +(defvar opam-tools + '(("tuareg" . opam-setup-tuareg) + ("ocp-indent" . opam-setup-ocp-indent) + ("ocp-index" . opam-setup-ocp-index) + ("merlin" . opam-setup-merlin) + ("utop" . opam-setup-utop))) + +(defun opam-detect-installed-tools () + (let* + ((command "opam list --installed --short --safe --color=never") + (names (mapcar 'car opam-tools)) + (command-string (mapconcat 'identity (cons command names) " ")) + (reply (opam-shell-command-to-string command-string))) + (when reply (split-string reply)))) + +(defvar opam-tools-installed (opam-detect-installed-tools)) + +(defun opam-auto-tools-setup () + (interactive) + (dolist (tool opam-tools) + (when (member (car tool) opam-tools-installed) + (funcall (symbol-function (cdr tool)))))) + +(opam-auto-tools-setup) +;; ## end of OPAM user-setup addition for emacs / base ## keep this line +;; ## added by OPAM user-setup for emacs / tuareg ## b10f42abebd2259b784b70d1a7f7e426 ## you can edit, but keep this line +;; Set to autoload tuareg from its original switch when not found in current +;; switch (don't load tuareg-site-file as it adds unwanted load-paths) +(defun opam-tuareg-autoload (fct file doc args) + (let ((load-path (cons "/home/wpcarro/.opam/default/share/emacs/site-lisp" load-path))) + (load file)) + (apply fct args)) +(when (not (member "tuareg" opam-tools-installed)) + (defun tuareg-mode (&rest args) + (opam-tuareg-autoload 'tuareg-mode "tuareg" "Major mode for editing OCaml code" args)) + (defun tuareg-run-ocaml (&rest args) + (opam-tuareg-autoload 'tuareg-run-ocaml "tuareg" "Run an OCaml toplevel process" args)) + (defun ocamldebug (&rest args) + (opam-tuareg-autoload 'ocamldebug "ocamldebug" "Run the OCaml debugger" args)) + (defalias 'run-ocaml 'tuareg-run-ocaml) + (defalias 'camldebug 'ocamldebug) + (add-to-list 'auto-mode-alist '("\\.ml[iylp]?\\'" . tuareg-mode)) + (add-to-list 'auto-mode-alist '("\\.eliomi?\\'" . tuareg-mode)) + (add-to-list 'interpreter-mode-alist '("ocamlrun" . tuareg-mode)) + (add-to-list 'interpreter-mode-alist '("ocaml" . tuareg-mode)) + (dolist (ext '(".cmo" ".cmx" ".cma" ".cmxa" ".cmxs" ".cmt" ".cmti" ".cmi" ".annot")) + (add-to-list 'completion-ignored-extensions ext))) +;; ## end of OPAM user-setup addition for emacs / tuareg ## keep this line diff --git a/users/wpcarro/emacs/.emacs.d/snippets/c-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdio b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdio new file mode 100644 index 000000000..52bc717e4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdio @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: +# key: sio +# -- +#include \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdlib b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdlib new file mode 100644 index 000000000..5d44e8ed7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdlib @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: +# key: slb +# -- +#include \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/c-mode/struct b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/struct new file mode 100644 index 000000000..6e9282f83 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/struct @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: struct +# key: struct +# -- +typedef struct $1 { + $2 +} $1_t; \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/elisp-module-docs b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/elisp-module-docs new file mode 100644 index 000000000..8ea7b8f07 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/elisp-module-docs @@ -0,0 +1,11 @@ +# -*- mode: snippet -*- +# name: Elisp module docs +# key: emd +# -- +;;; `(-> (buffer-file-name) f-filename)` --- $2 -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; $3 + +;;; Code: \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/function new file mode 100644 index 000000000..bfa888d52 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/function @@ -0,0 +1,8 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# expand-env: ((yas-indent-line 'fixed)) +# -- +(defun $1 ($2) + "$3" + $4) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/generic-header b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/generic-header new file mode 100644 index 000000000..bf6e525f8 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/generic-header @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Header +# key: hdr +# -- +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; $1 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/library-header b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/library-header new file mode 100644 index 000000000..0f0ad5c4f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/library-header @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Library header +# key: lib +# -- +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/provide-footer b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/provide-footer new file mode 100644 index 000000000..2a0bcc33f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/provide-footer @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Provide footer +# key: elf +# -- +(provide '`(-> (buffer-file-name) f-filename f-no-ext)`) +;;; `(-> (buffer-file-name) f-filename)` ends here \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/derive-safe-copy b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/derive-safe-copy new file mode 100644 index 000000000..95f7d9dee --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/derive-safe-copy @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Derive Safe Copy +# key: dsc +# -- +deriveSafeCopy 0 'base ''$1 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/import-qualified b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/import-qualified new file mode 100644 index 000000000..4c4db62a8 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/import-qualified @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Import qualified +# key: iq +# -- +import qualified $1 as $2 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/instance-defn b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/instance-defn new file mode 100644 index 000000000..10d194ce4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/instance-defn @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Instance +# key: inst +# -- +instance $1 where + $2 = $3 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/language-extension b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/language-extension new file mode 100644 index 000000000..9d6084acb --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/language-extension @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: language extension +# key: lang +# -- +{-# LANGUAGE $1 #-} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/separator b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/separator new file mode 100644 index 000000000..1ab0d762b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/separator @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Separator +# key: - +# -- +-------------------------------------------------------------------------------- \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/undefined b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/undefined new file mode 100644 index 000000000..7609f801f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/undefined @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Undefiend +# key: nd +# -- +undefined \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/html-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/html-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/html-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/html-mode/index-boilerplate b/users/wpcarro/emacs/.emacs.d/snippets/html-mode/index-boilerplate new file mode 100644 index 000000000..3cea6ce00 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/html-mode/index-boilerplate @@ -0,0 +1,18 @@ +# -*- mode: snippet -*- +# name: HTML index.html starter +# key: html +# -- + + + + + + $1 + + + + + + + + \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/java-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/java-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/java-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/java-mode/public-static-void-main b/users/wpcarro/emacs/.emacs.d/snippets/java-mode/public-static-void-main new file mode 100644 index 000000000..1839a27eb --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/java-mode/public-static-void-main @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: public static void main +# key: psvm +# -- +public static void main(String[] args) { + $1 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/defpackage b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/defpackage new file mode 100644 index 000000000..7f110a971 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/defpackage @@ -0,0 +1,9 @@ +# -*- mode: snippet -*- +# name: Define package +# key: defp +# -- +(in-package #:cl-user) +(defpackage #:$1 + (:documentation "$2") + (:use #:cl)) +(in-package #:$1) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/function new file mode 100644 index 000000000..b1769cd3d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# -- +(defun $1 ($2) + "$3" + $4) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/typed-function b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/typed-function new file mode 100644 index 000000000..a3c236821 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/typed-function @@ -0,0 +1,8 @@ +# -*- mode: snippet -*- +# name: Typed function +# key: tfn +# -- +(type $1 ($3) $4) +(defun $1 ($2) + "$5" + $6) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/shell-nix b/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/shell-nix new file mode 100644 index 000000000..45cb24e2b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/shell-nix @@ -0,0 +1,13 @@ +# -*- mode: snippet -*- +# name: shell.nix boilerplate +# key: import +# -- +let + briefcase = with import {}; + pkgs = briefcase.third_party.pkgs; +in stdenv.mkDerivation { + name = "$1"; + buildInputs = [ + $2 + ]; +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/org-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/org-mode/code-snippet b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/code-snippet new file mode 100644 index 000000000..4215b1599 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/code-snippet @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Code Snippet +# key: src +# -- +#+BEGIN_SRC $1 +$2 +#+END_SRC \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/org-mode/href b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/href new file mode 100644 index 000000000..ac65ea2e4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/href @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Org mode URL +# key: href +# -- +[[$1][$2]] \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/dunder-main b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/dunder-main new file mode 100644 index 000000000..4dd22dc0b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/dunder-main @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Dunder main (__main__) +# key: mn +# -- +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/function new file mode 100644 index 000000000..379ceda1a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/function @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# -- +def $1($2): + $3 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/header b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/header new file mode 100644 index 000000000..db48adfec --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/header @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Header +# key: hdr +# -- +################################################################################ +# $1 +################################################################################ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/init b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/init new file mode 100644 index 000000000..5c407495f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/init @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: dunder init +# key: ctor +# -- +def __init__(self$1): + $2 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/shebang b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/shebang new file mode 100644 index 000000000..0f45ae782 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/shebang @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: shebang +# key: shb +# -- +#!/usr/bin/env python +# -*- coding: utf-8 -*- diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/utf-8 b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/utf-8 new file mode 100644 index 000000000..3babc7303 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/utf-8 @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: utf-8 +# key: utf +# -- +# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/function new file mode 100644 index 000000000..882c48ded --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/function @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# -- +(define ($1) $2) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda new file mode 100644 index 000000000..b9a684588 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Lambda function +# key: ld +# -- +(λ ($1) $2) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda-symbol b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda-symbol new file mode 100644 index 000000000..254b9fd96 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda-symbol @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Lambda symbol +# key: l +# -- +λ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/function new file mode 100644 index 000000000..6b4b6a5db --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# -- +let $1 = (~$2:$3) => { + $4 +}; \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/switch b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/switch new file mode 100644 index 000000000..40f34ff8d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/switch @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Switch statement +# key: sw +# -- +switch ($1) { +| $2 => +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/action-extractor b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/action-extractor new file mode 100644 index 000000000..62834a29a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/action-extractor @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: exactness +# key: $x +# -- +$Exact<$Call> \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/console-log b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/console-log new file mode 100644 index 000000000..82ec3fd8e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/console-log @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Console.log helper +# key: clg +# -- +console.log($1) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-defn b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-defn new file mode 100644 index 000000000..8e35e61fc --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-defn @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: const definition +# key: cn +# -- +const $1 = '$2' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-function b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-function new file mode 100644 index 000000000..13f2018f2 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: const function +# key: cfn +# -- +const $1 = ($2) => { + $3 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/destructure-const b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/destructure-const new file mode 100644 index 000000000..2a52c57c7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/destructure-const @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Destructuring a const +# key: cds +# -- +const { $1 } = $2 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow new file mode 100644 index 000000000..187a2efc5 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Fat arrow function +# key: fa +# -- +=> \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow-function b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow-function new file mode 100644 index 000000000..694914a83 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow-function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Fat arrow function +# key: faf +# -- +() => { + $1 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-destructured b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-destructured new file mode 100644 index 000000000..ded3ce163 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-destructured @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Import destructured +# key: ids +# -- +import { $1 } from '$2' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-react b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-react new file mode 100644 index 000000000..0463f5cd5 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-react @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Import React dependency (ES6) +# key: ir +# -- +import React from 'react' diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-type b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-type new file mode 100644 index 000000000..fcd51f687 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-type @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: import type +# key: ixt +# -- +import type { $1 } from '$2' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-x-from-y b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-x-from-y new file mode 100644 index 000000000..09fa6df50 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-x-from-y @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: import x from y +# key: ix +# -- +import $1 from '$2' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-y b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-y new file mode 100644 index 000000000..9f550e300 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-y @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: import y +# key: iy +# -- +import '$1' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-describe-test b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-describe-test new file mode 100644 index 000000000..ed382d4f7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-describe-test @@ -0,0 +1,10 @@ +# -*- mode: snippet -*- +# name: Jest describe/test block +# key: dsc +# -- +describe('$1', () => { + test('$2', () => { + + expect($3).toEqual($4) + }) +}) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-test b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-test new file mode 100644 index 000000000..12ca2e786 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-test @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Jest / Jasmine test +# key: tst +# -- +test('$1', () => { + expect($2).toBe($3) +}) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/react-class-component b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/react-class-component new file mode 100644 index 000000000..f2a93a31d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/react-class-component @@ -0,0 +1,11 @@ +# -*- mode: snippet -*- +# name: React class extends +# key: clz +# -- +class $1 extends React.Component { + render() { + $2 + } +} + +export default $1 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/redux-action b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/redux-action new file mode 100644 index 000000000..681c5d0df --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/redux-action @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: redux-action +# key: rax +# -- +export const ${1:$$(string-lower->caps yas-text)} = '`(downcase (functions-buffer-dirname))`/${1:$(string-caps->kebab yas-text)}' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/typed-redux-action b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/typed-redux-action new file mode 100644 index 000000000..53c6e5fc5 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/typed-redux-action @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: typed-redux-action +# key: trax +# -- +export const ${1:$$(string-lower->caps yas-text)}: '`(downcase (functions-buffer-dirname))`/${1:$(string-caps->kebab yas-text)}' = '`(downcase (buffer-dirname))`/${1:$(string-caps->kebab yas-text)}' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/for-loop b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/for-loop new file mode 100644 index 000000000..4d8e0e3bb --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/for-loop @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: for-loop +# key: for +# -- +for $1 in $2 { + $3 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/match b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/match new file mode 100644 index 000000000..bf0e876e2 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/match @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: match +# key: match +# -- +match $1 { + $2 => $3, +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/function new file mode 100644 index 000000000..efa946bb2 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Create function +# key: fn +# -- +$1() { + $2 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/text-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/text-mode/check-mark b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/check-mark new file mode 100644 index 000000000..797781968 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/check-mark @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Unicode checkmark +# key: uck +# -- +✓ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/text-mode/x-mark b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/x-mark new file mode 100644 index 000000000..bc3c356a6 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/x-mark @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Unicode ex-mark +# key: ux +# -- +✗ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/web-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/.yas-parents new file mode 100644 index 000000000..d58dacb7a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/web-mode/header b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/header new file mode 100644 index 000000000..ae59c7a50 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/header @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Header +# key: hdr +# -- +/******************************************************************************* + * $1 + ******************************************************************************/ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/web-mode/index-boilerplate b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/index-boilerplate new file mode 100644 index 000000000..b791cdf86 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/index-boilerplate @@ -0,0 +1,18 @@ +# -*- mode: snippet -*- +# name: HTML index.html starter +# key: html +# -- + + + + + + $1 + + + + + + + + diff --git a/users/wpcarro/emacs/.emacs.d/vendor/dired+.el b/users/wpcarro/emacs/.emacs.d/vendor/dired+.el new file mode 100644 index 000000000..2403b0af9 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/vendor/dired+.el @@ -0,0 +1,13696 @@ +;;; dired+.el --- Extensions to Dired. +;; +;; Filename: dired+.el +;; Description: Extensions to Dired. +;; Author: Drew Adams +;; Maintainer: Drew Adams (concat "drew.adams" "@" "oracle" ".com") +;; Copyright (C) 1999-2019, Drew Adams, all rights reserved. +;; Created: Fri Mar 19 15:58:58 1999 +;; Version: 2019.04.21 +;; Package-Requires: () +;; Last-Updated: Sun Jul 21 09:47:33 2019 (-0700) +;; By: dradams +;; Update #: 11727 +;; URL: https://www.emacswiki.org/emacs/download/dired%2b.el +;; Doc URL: https://www.emacswiki.org/emacs/DiredPlus +;; Keywords: unix, mouse, directories, diredp, dired +;; Compatibility: GNU Emacs: 20.x, 21.x, 22.x, 23.x, 24.x, 25.x, 26.x +;; +;; Features that might be required by this library: +;; +;; `apropos', `apropos+', `autofit-frame', `avoid', `backquote', +;; `bookmark', `bookmark+', `bookmark+-1', `bookmark+-bmu', +;; `bookmark+-key', `bookmark+-lit', `button', `bytecomp', `cconv', +;; `cl', `cl-lib', `cmds-menu', `col-highlight', `crosshairs', +;; `dired', `dired+', `dired-aux', `dired-loaddefs', `dired-x', +;; `easymenu', `fit-frame', `font-lock', `font-lock+', +;; `format-spec', `frame-fns', `gv', `help+', `help-fns', +;; `help-fns+', `help-macro', `help-macro+', `help-mode', +;; `highlight', `hl-line', `hl-line+', `image', `image-dired', +;; `image-file', `image-mode', `info', `info+', `kmacro', +;; `macroexp', `menu-bar', `menu-bar+', `misc-cmds', `misc-fns', +;; `naked', `pp', `pp+', `radix-tree', `replace', `second-sel', +;; `strings', `syntax', `text-mode', `thingatpt', `thingatpt+', +;; `vline', `w32-browser', `w32browser-dlgopen', `wid-edit', +;; `wid-edit+', `widget'. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Commentary: +;; +;; Extensions to Dired. +;; +;; This file extends functionalities provided by standard GNU Emacs +;; files `dired.el', `dired-aux.el', and `dired-x.el'. +;; +;; Key bindings changed. Menus redefined. `diredp-mouse-3-menu' +;; popup menu added. New commands. Some commands enhanced. +;; +;; All of the new functions, variables, and faces defined here have +;; the prefix `diredp-' (for Dired Plus) in their names. +;; +;; +;; Wraparound Navigation +;; --------------------- +;; +;; In vanilla Dired, `dired-next-marked-file' (`M-}' or `* C-n') and +;; `dired-previous-marked-file' (`M-{' or `* C-p') wrap around when +;; you get to the end or the beginning of the Dired buffer. Handy. +;; +;; But the other navigation commands do not wrap around. In `Dired+' +;; they do, provided option `diredp-wrap-around-flag' is non-nil, +;; which it is by default. This means the following commands: +;; +;; `diredp-next-line' - `n', `C-n', `down', `SPC' +;; `diredp-previous-line' - `p', `C-p', `up' +;; `diredp-next-dirline' - `>' +;; `diredp-prev-dirline' - `<' +;; `diredp-next-subdir' - `C-M-n' +;; `diredp-prev-subdir' - `C-M-p' +;; +;; +;; Quick Viewing While Navigating +;; ------------------------------ +;; +;; You can use key `C-down' or `C-up' to navigate to the next or +;; previous file line, respectively, and at the same time show its +;; file in another window. The focus remains on the Dired buffer. +;; A numeric prefix arg means move that many lines first. +;; +;; Names of files and directories that match either of the options +;; `diredp-visit-ignore-extensions' or `diredp-visit-ignore-regexps' +;; are skipped. +;; +;; You can use `e' to show the file of the current line. If it is +;; already shown in the same frame, and if Dired is the only other +;; window there, then the file is hidden (its window is deleted). +;; +;; +;; Font-Lock Highlighting +;; ---------------------- +;; +;; If you want a maximum or minimum fontification for Dired mode, +;; then customize option `font-lock-maximum-decoration'. If you want +;; a different fontification level for Dired than for other modes, +;; you can do this too by customizing +;; `font-lock-maximize-decoration'. +;; +;; A few of the user options defined here have an effect on +;; font-locking, and this effect is established only when Dired+ is +;; loaded, which defines the font-lock keywords for Dired. These +;; options include `diredp-compressed-extensions', +;; `diredp-ignore-compressed-flag', `dired-omit-extensions', and +;; `diredp-omit-files-regexp'. This means that if you change the +;; value of such an option then you will see the change only in a new +;; Emacs session. +;; +;; (You can see the effect in the same session if you use `C-M-x' on +;; the `defvar' sexp for `diredp-font-lock-keywords-1', and then you +;; toggle font-lock off and back on.) +;; +;; +;; Act on All Files +;; ---------------- +;; +;; Most of the commands (such as `C' and `M-g') that operate on the +;; marked files have the added feature here that multiple `C-u' use +;; not the files that are marked or the next or previous N files, but +;; *all* of the files in the Dired buffer. Just what "all" files +;; means changes with the number of `C-u', as follows: +;; +;; `C-u C-u' - Use all files present, but no directories. +;; `C-u C-u C-u' - Use all files and dirs except `.' and `..'. +;; `C-u C-u C-u C-u' - use all files and dirs, `.' and `..'. +;; +;; (More than four `C-u' act the same as two.) +;; +;; This feature can be particularly useful when you have a Dired +;; buffer with files chosen from multiple directories. +;; +;; Note that in most cases this behavior is described only in the doc +;; string of function `dired-get-marked-files'. It is generally +;; *not* described in the doc strings of the various commands, +;; because that would require redefining each command separately +;; here. Instead, we redefine macro `dired-map-over-marks' and +;; function `dired-get-filename' in order to achieve this effect. +;; +;; Commands such as `dired-do-load' for which it does not make sense +;; to act on directories generally treat more than two `C-u' the same +;; as two `C-u'. +;; +;; Exceptions to the general behavior described here are called out +;; in the doc strings. In particular, the behavior of a prefix arg +;; for `dired-do-query-replace-regexp' is different, so that you can +;; use it also to specify word-delimited replacement. +;; +;; +;; Act on Marked (or All) Files Here and Below +;; ------------------------------------------- +;; +;; The prefix argument behavior just described does not apply to the +;; `diredp-*-recursive' commands. These commands act on the marked +;; files in the current Dired buffer or on all files in the directory +;; if none are marked. +;; +;; But these commands also handle marked subdirectories recursively, +;; in the same way. That is, they act also on the marked files in +;; any marked subdirectories, found recursively. If such a +;; descendant directory is listed in a Dired buffer then its marked +;; files and subdirs are handled the same way. If there is no Dired +;; buffer that lists a given marked subdirectory then all of its +;; files and subdirs are acted on. +;; +;; For most such here-and-below commands, a prefix argument means +;; ignore all marks. The commands then act on all files in the +;; current Dired buffer and all of its subdirectories, recursively. +;; +;; But here-and-below commands that unmark or change marks act +;; differently for different kinds of prefix argument: +;; +;; * A non-positive prefix arg means ignore subdir markings and act +;; instead on ALL subdirs. +;; +;; * A non-negative prefix arg means do not change marks on subdirs +;; themselves. +;; +;; For example, `M-+ U' removes all marks, including from marked +;; subdirs, recursively. `C-- M-+ U' removes them from all files in +;; all subdirs (marked or not), recursively. `C-9 M-+ U' removes all +;; marks, recursively, except the marks on subdirs themselves. `C-0 +;; M-+ U' acts like those two combined: it descends everywhere, +;; ignoring which subdirs are marked, but it does not remove marks +;; from subdirs themselves. +;; +;; All of the `diredp-*-recursive' commands are on prefix key `M-+', +;; and most are available on submenu `Marked Here and Below' of the +;; `Multiple' menu-bar menu. The commands that unmark and change +;; marks are also in submenu `Here and Below' of menu-bar menu +;; `Marks'. +;; +;; If you use library `Icicles' then you have the following +;; additional commands/keys that act recursively on marked files. +;; They are in the `Icicles' submenu of menu `Multiple' > `Marked +;; Here and Below'. +;; +;; * `M-+ M-s M-s' or `M-s M-s m' - Use Icicles search (and its +;; on-demand replace) on the marked files. +;; +;; * Save the names of the marked files: +;; +;; `M-+ C-M->' - Save as a completion set, for use during +;; completion (e.g. with `C-x C-f'). +;; +;; `M-+ C->' - Add marked names to the names in the current saved +;; completion set. +;; +;; `M-+ C-}' - Save persistently to an Icicles cache file, for +;; use during completion in another session. +;; +;; `icicle-dired-save-marked-to-fileset-recursive' - Like `M-+ +;; C-}', but save persistently to an Emacs fileset. +;; +;; `M-+ C-M-}' - Save to a Lisp variable. +;; +;; +;; In the other direction, if you have a saved set of file names then +;; you can use `C-M-<' (`icicle-dired-chosen-files-other-window') in +;; Dired to open a Dired buffer for just those files. So you can +;; mark some files and subdirs in a hierarchy of Dired buffers, use +;; `M-+ C-}' to save their names persistently, then later use `C-{' +;; to retrieve them, and `C-M-<' (in Dired) to open Dired on them. +;; +;; +;; Image Files +;; ----------- +;; +;; `Dired+' provides several enhancements regarding image files. +;; Most of these require standard library `image-dired.el'. One of +;; them, command `diredp-do-display-images', which displays all of +;; the marked image files, requires standard library `image-file.el'. +;; +;; `Dired+' loads these libraries automatically, if available, which +;; means an Emacs version that supports image display (Emacs 22 or +;; later). (You must of course have installed whatever else your +;; Emacs version needs to display images.) +;; +;; Besides command `diredp-do-display-images', see the commands whose +;; names have prefix `diredp-image-'. And see options +;; `diredp-image-preview-in-tooltip' and +;; `diredp-auto-focus-frame-for-thumbnail-tooltip-flag'. +;; +;; +;; Inserted Subdirs, Multiple Dired Buffers, Files from Anywhere,... +;; ----------------------------------------------------------------- +;; +;; These three standard Dired features are worth pointing out. The +;; third in particular is little known because (a) it is limited in +;; vanilla Dired and (b) you cannot use it interactively. +;; +;; * You can pass a glob pattern with wildcards to `dired' +;; interactively, as the file name. +;; +;; * You can insert multiple subdirectory listings into a single +;; Dired buffer using `i' on each subdir line. Use `C-u i' to +;; specify `ls' switches. Specifying switch `R' inserts the +;; inserted subdirectory's subdirs also, recursively. You can +;; also use `i' to bounce between a subdirectory line and its +;; inserted-listing header line. You can delete a subdir listing +;; using `C-u k' on its header line. You can hide/show an +;; inserted subdir using `$'. You can use `C-_' to undo any of +;; these operations. +;; +;; * You can open a Dired buffer for an arbitrary set of files from +;; different directories. You do this by invoking `dired' +;; non-interactively, passing it a cons of a Dired buffer name and +;; the file names. Relative file names are interpreted relative +;; to the value of `default-directory'. Use absolute file names +;; when appropriate. +;; +;; `Dired+' makes these features more useful. +;; +;; `$' is improved: It is a simple toggle - it does not move the +;; cursor forward. `M-$' advances the cursor, in addition to +;; toggling like `$'. `C-u $' does hide/show all (what `M-$' does in +;; vanilla Dired). +;; +;; `i' is improved in these ways: +;; +;; * Once a subdir has been inserted, `i' bounces between the subdir +;; listing and the subdir line in the parent listing. If the +;; parent dir is hidden, then `i' from a subdir opens the parent +;; listing so it can move to the subdir line there (Emacs 24+). +;; +;; * Vanilla Dired lets you create a Dired listing with files and +;; directories from arbitrary locations, but you cannot insert +;; (`i') such a directory if it is not in the same directory tree +;; as the `default-directory' used to create the Dired buffer. +;; `Dired+' removes this limitation; you can insert any non-root +;; directories (that is, not `/', `c:/', etc.). +;; +;; `Dired+' lets you create Dired buffers that contain arbitrary +;; files and directories interactively, not just using Lisp. Just +;; use a non-positive prefix arg (e.g., `C--') when invoking `dired'. +;; +;; You are then prompted for the Dired buffer name (anything you +;; like, not necessarily a directory name) and the individual files +;; and directories that you want listed. +;; +;; A non-negative prefix arg still prompts you for the `ls' switches +;; to use. (So `C-0' does both: prompts for `ls' switches and for +;; the Dired buffer name and the files to list.) +;; +;; `Dired+' adds commands for combining and augmenting Dired +;; listings: +;; +;; * `diredp-add-to-dired-buffer', bound globally to `C-x D A', lets +;; you add arbitrary file and directory names to an existing Dired +;; buffer. +;; +;; * `diredp-dired-union', bound globally to `C-x D U', lets you +;; take the union of multiple Dired listings, or convert an +;; ordinary Dired listing to an explicit list of absolute file +;; names. With a non-positive prefix arg, you can add extra file +;; and directory names, just as for `diredp-add-to-dired-buffer'. +;; +;; You can optionally add a header line to a Dired buffer using +;; toggle command `diredp-breadcrumbs-in-header-line-mode'. (A +;; header line remains at the top of the window - no need to scroll +;; to see it.) If you want to show the header line automatically in +;; all Dired buffers, you can do this: +;; +;; (add-hook 'dired-before-readin-hook +;; 'diredp-breadcrumbs-in-header-line-mode) +;; +;; Some other libraries, such as `Bookmark+' and `Icicles', make it +;; easy to create or re-create Dired buffers that list specific files +;; and have a particular set of markings. `Bookmark+' records Dired +;; buffers persistently, remembering `ls' switches, markings, subdir +;; insertions, and hidden subdirs. If you use `Icicles' then `dired' +;; is a multi-command: you can open multiple Dired buffers with one +;; `dired' invocation. +;; +;; Dired can help you manage projects. You might have multiple Dired +;; buffers with quite specific contents. You might have some +;; subdirectories inserted in the same Dired buffer, and you might +;; have separate Dired buffers for some subdirectories. Sometimes it +;; is useful to have both for the same subdirectory. And sometimes +;; it is useful to move from one presentation to the other. +;; +;; This is one motivation for the `Dired+' `diredp-*-recursive' +;; commands, which act on the marked files in marked subdirectories, +;; recursively. In one sense, these commands are an alternative to +;; using a single Dired buffer with inserted subdirectories. They +;; let you use the same operations on the files in a set of Dired +;; directories, without inserting those directories into an ancestor +;; Dired buffer. +;; +;; You can use command `diredp-dired-inserted-subdirs' to open a +;; separate Dired buffer for each of the subdirs that is inserted in +;; the current Dired buffer. Markings and Dired switches are +;; preserved. +;; +;; In the opposite direction, if you use `Icicles' then you can use +;; multi-command `icicle-dired-insert-as-subdir', which lets you +;; insert any number of directories you choose interactively into a +;; Dired ancestor directory listing. If a directory you choose to +;; insert already has its own Dired buffer, then its markings and +;; switches are preserved for the new, subdirectory listing in the +;; ancestor Dired buffer. +;; +;; +;; Hide/Show Details +;; ----------------- +;; +;; Starting with Emacs 24.4, listing details are hidden by default. +;; Note that this is different from the vanilla Emacs behavior, which +;; is to show details by default. +;; +;; Use `(' anytime to toggle this hiding. You can use option +;; `diredp-hide-details-initially-flag' to change the default/initial +;; state. See also option `diredp-hide-details-propagate-flag'. +;; +;; NOTE: If you do not want to hide details initially then you must +;; either (1) change `diredp-hide-details-initially-flag' using +;; Customize (recommended) or (2) set it to `nil' (e.g., using +;; `setq') *BEFORE* loading `dired+.el'. +;; +;; If you have an Emacs version older than 24.4, you can use library +;; `dired-details+.el' (plus `dired-details.el') to get similar +;; behavior. +;; +;; +;; Mode-Line +;; --------- +;; +;; The number of files and dirs that are marked with `*', and the +;; number that are flagged for deletion (marked `D') are indicated in +;; the mode-line. When the cursor is on such a line the indication +;; tells you how many more there are. For example, if the cursor is +;; on the line of the third file that is marked `*', and there are +;; seven of them total, then the mode-line shows `3/7*'. +;; +;; The mode-line also indicates, for the current listing (which could +;; be a subdir listing), how many files and dirs are listed. If the +;; cursor is on the 27th file in a listing of 78 files then the +;; mode-line shows 27/78. +;; +;; For counting files and dirs in a listing, option +;; `diredp-count-.-and-..-flag' controls whether to count the lines +;; for `.' and `..'. By default it is nil, meaning they are not +;; counted. +;; +;; +;; If You Use Dired+ in Terminal Mode +;; ---------------------------------- +;; +;; By default, Dired+ binds some keys that can be problematic in some +;; terminals when you use Emacs in terminal mode (i.e., `emacs -nw'). +;; This is controlled by option +;; `diredp-bind-problematic-terminal-keys'. +;; +;; In particular, keys that use modifiers Meta and Shift together can +;; be problematic. If you use Dired+ in text-only terminal, and you +;; find that your terminal does not support such keys, then you might +;; want to customize the option to set the value to `nil', and then +;; bind the commands to some other keys, which your terminal +;; supports. +;; +;; The problematic keys used by Dired+ include these: +;; +;; `M-M' (aka `M-S-m') - `diredp-chmod-this-file' +;; `M-O' (aka `M-S-o') - `diredp-chown-this-file' +;; `M-T' (aka `M-S-t') - `diredp-touch-this-file' +;; `C-M-B' (aka `C-M-S-b') - `diredp-do-bookmark-in-bookmark-file' +;; `C-M-G' (aka `C-M-S-g') - `diredp-chgrp-this-file' +;; `C-M-R' (aka `C-M-S-r') - `diredp-toggle-find-file-reuse-dir' +;; `C-M-T' (aka `C-M-S-t') - `dired-do-touch' +;; `M-+ M-B' (aka `M-+ M-S-b') - +;; `diredp-do-bookmark-dirs-recursive' +;; `M-+ C-M-B' (aka `M-+ C-M-S-b') - +;; `diredp-do-bookmark-in-bookmark-file-recursive' +;; `M-+ C-M-T' (aka `M-+ C-M-S-t') - `diredp-do-touch-recursive' +;; +;; (See also `(info "(org) TTY keys")' for more information about +;; keys that can be problematic in a text-only terminal.) +;; +;; +;; Faces defined here: +;; +;; `diredp-autofile-name', `diredp-compressed-file-suffix', +;; `diredp-date-time', `diredp-deletion', +;; `diredp-deletion-file-name', `diredp-dir-heading', +;; `diredp-dir-priv', `diredp-exec-priv', `diredp-executable-tag', +;; `diredp-file-name', `diredp-file-suffix', `diredp-flag-mark', +;; `diredp-flag-mark-line', `diredp-get-file-or-dir-name', +;; `diredp-ignored-file-name', `diredp-link-priv', +;; `diredp-mode-line-flagged', `diredp-mode-line-marked' +;; `diredp-omit-file-name', `diredp-no-priv', `diredp-number', +;; `diredp-other-priv', `diredp-rare-priv', `diredp-read-priv', +;; `diredp-symlink', `diredp-tagged-autofile-name', +;; `diredp-write-priv'. +;; +;; Commands defined here: +;; +;; `diredp-add-to-dired-buffer', `diredp-add-to-this-dired-buffer', +;; `diredp-do-apply-function', +;; `diredp-do-apply-function-recursive', +;; `diredp-async-shell-command-this-file', +;; `diredp-bookmark-this-file', +;; `diredp-breadcrumbs-in-header-line-mode' (Emacs 22+), +;; `diredp-byte-compile-this-file', `diredp-capitalize', +;; `diredp-capitalize-recursive', `diredp-capitalize-this-file', +;; `diredp-change-marks-recursive' (Emacs 22+), +;; `diredp-chgrp-this-file', `diredp-chmod-this-file', +;; `diredp-chown-this-file', +;; `diredp-compilation-files-other-window' (Emacs 24+), +;; `diredp-compress-this-file', +;; `diredp-copy-abs-filenames-as-kill', +;; `diredp-copy-abs-filenames-as-kill-recursive', +;; `diredp-copy-filename-as-kill-recursive', +;; `diredp-copy-tags-this-file', `diredp-copy-this-file', +;; `diredp-decrypt-this-file', `diredp-delete-this-file', +;; `diredp-describe-autofile', `diredp-describe-file', +;; `diredp-describe-marked-autofiles', `diredp-describe-mode', +;; `diredp-dired-for-files', `diredp-dired-for-files-other-window', +;; `diredp-dired-inserted-subdirs', `diredp-dired-plus-help', +;; `diredp-dired-recent-dirs', +;; `diredp-dired-recent-dirs-other-window', +;; `diredp-dired-this-subdir', `diredp-dired-union', +;; `diredp-do-async-shell-command-recursive', `diredp-do-bookmark', +;; `diredp-do-bookmark-dirs-recursive', +;; `diredp-do-bookmark-in-bookmark-file', +;; `diredp-do-bookmark-in-bookmark-file-recursive', +;; `diredp-do-bookmark-recursive', `diredp-do-chmod-recursive', +;; `diredp-do-chgrp-recursive', `diredp-do-chown-recursive', +;; `diredp-do-copy-recursive', `diredp-do-decrypt-recursive', +;; `diredp-do-delete-recursive', `diredp-do-display-images' (Emacs +;; 22+), `diredp-do-emacs-command', `diredp-do-encrypt-recursive', +;; `diredp-do-find-marked-files-recursive', `diredp-do-grep', +;; `diredp-do-grep-recursive', `diredp-do-hardlink-recursive', +;; `diredp-do-isearch-recursive', +;; `diredp-do-isearch-regexp-recursive', `diredp-do-lisp-sexp' +;; (Emacs 22+), `diredp-do-move-recursive', +;; `diredp-do-paste-add-tags', `diredp-do-paste-replace-tags', +;; `diredp-do-print-recursive', +;; `diredp-do-query-replace-regexp-recursive', +;; `diredp-do-redisplay-recursive', +;; `diredp-do-relsymlink-recursive', `diredp-do-remove-all-tags', +;; `diredp-do-search-recursive', `diredp-do-set-tag-value', +;; `diredp-do-shell-command-recursive', `diredp-do-sign-recursive', +;; `diredp-do-symlink-recursive', `diredp-do-tag', +;; `diredp-do-touch-recursive', `diredp-do-untag', +;; `diredp-do-verify-recursive', `diredp-downcase-recursive', +;; `diredp-downcase-this-file', `diredp-ediff', +;; `diredp-encrypt-this-file', `diredp-fileset', +;; `diredp-fileset-other-window', `diredp-find-a-file', +;; `diredp-find-a-file-other-frame', +;; `diredp-find-a-file-other-window', +;; `diredp-find-file-other-frame', +;; `diredp-find-file-reuse-dir-buffer', +;; `diredp-find-line-file-other-window', +;; `diredp-flag-auto-save-files-recursive', +;; `diredp-flag-region-files-for-deletion', +;; `diredp-grepped-files-other-window', `diredp-grep-this-file', +;; `diredp-hardlink-this-file', `diredp-highlight-autofiles-mode', +;; `diredp-image-dired-comment-file', +;; `diredp-image-dired-comment-files-recursive', +;; `diredp-image-dired-copy-with-exif-name', +;; `diredp-image-dired-create-thumb', +;; `diredp-image-dired-delete-tag', +;; `diredp-image-dired-delete-tag-recursive', +;; `diredp-image-dired-display-thumb', +;; `diredp-image-dired-display-thumbs-recursive', +;; `diredp-image-dired-edit-comment-and-tags', +;; `diredp-image-dired-tag-file', +;; `diredp-image-dired-tag-files-recursive', +;; `diredp-image-show-this-file', `diredp-insert-as-subdir', +;; `diredp-insert-subdirs', `diredp-insert-subdirs-recursive', +;; `diredp-kill-this-tree', `diredp-list-marked-recursive', +;; `diredp-load-this-file', `diredp-mark-autofiles', +;; `diredp-marked', `diredp-marked-other-window', +;; `diredp-marked-recursive', +;; `diredp-marked-recursive-other-window', +;; `diredp-mark-extension-recursive', +;; `diredp-mark-files-containing-regexp-recursive', +;; `diredp-mark-files-regexp-recursive', +;; `diredp-mark-files-tagged-all', `diredp-mark-files-tagged-none', +;; `diredp-mark-files-tagged-not-all', +;; `diredp-mark-files-tagged-some', +;; `diredp-mark-files-tagged-regexp', `diredp-mark-region-files', +;; `diredp-mark-sexp-recursive' (Emacs 22+), +;; `diredp-mark/unmark-autofiles', `diredp-mark/unmark-extension', +;; `diredp-mouse-3-menu', `diredp-mouse-backup-diff', +;; `diredp-mouse-copy-tags', `diredp-mouse-describe-autofile', +;; `diredp-mouse-describe-file', `diredp-mouse-diff', +;; `diredp-mouse-do-bookmark', `diredp-mouse-do-byte-compile', +;; `diredp-mouse-do-chgrp', `diredp-mouse-do-chmod', +;; `diredp-mouse-do-chown', `diredp-mouse-do-compress', +;; `diredp-mouse-do-copy', `diredp-mouse-do-delete', +;; `diredp-mouse-do-grep', `diredp-mouse-do-hardlink', +;; `diredp-mouse-do-load', `diredp-mouse-do-print', +;; `diredp-mouse-do-remove-all-tags', `diredp-mouse-do-rename', +;; `diredp-mouse-do-set-tag-value', +;; `diredp-mouse-do-shell-command', `diredp-mouse-do-symlink', +;; `diredp-mouse-do-tag', `diredp-mouse-do-untag', +;; `diredp-mouse-downcase', `diredp-mouse-ediff', +;; `diredp-mouse-find-line-file-other-window', +;; `diredp-mouse-find-file-other-frame', +;; `diredp-mouse-find-file-reuse-dir-buffer', +;; `diredp-mouse-flag-file-deletion', `diredp-mouse-mark', +;; `diredp-mouse-mark-region-files', `diredp-mouse-mark/unmark', +;; `diredp-mouse-unmark', `diredp-mouse-upcase', +;; `diredp-mouse-view-file', `diredp-move-file' (Emacs 24+), +;; `diredp-multiple-w32-browser-recursive', +;; `diredp-nb-marked-in-mode-name', `diredp-next-dirline', +;; `diredp-next-line', `diredp-next-subdir', `diredp-omit-marked', +;; `diredp-omit-unmarked', `diredp-paste-add-tags-this-file', +;; `diredp-paste-files', `diredp-paste-replace-tags-this-file', +;; `diredp-prev-dirline', `diredp-previous-line', +;; `diredp-prev-subdir', `diredp-print-this-file', +;; `diredp-relsymlink-this-file', +;; `diredp-remove-all-tags-this-file', `diredp-rename-this-file', +;; `diredp-send-bug-report', +;; `diredp-set-bookmark-file-bookmark-for-marked', +;; `diredp-set-bookmark-file-bookmark-for-marked-recursive', +;; `diredp-set-tag-value-this-file', +;; `diredp-shell-command-this-file', `diredp-show-metadata', +;; `diredp-show-metadata-for-marked', `diredp-sign-this-file', +;; `diredp-symlink-this-file', `diredp-tag-this-file', +;; `diredp-toggle-find-file-reuse-dir', +;; `diredp-toggle-marks-in-region', `diredp-touch-this-file', +;; `diredp-unmark-all-files-recursive' (Emacs 22+), +;; `diredp-unmark-all-marks-recursive' (Emacs 22+), +;; `diredp-unmark-autofiles', `diredp-unmark-files-tagged-all', +;; `diredp-unmark-files-tagged-none', +;; `diredp-unmark-files-tagged-not-all', +;; `diredp-unmark-files-tagged-some', `diredp-unmark-region-files', +;; `diredp-untag-this-file', `diredp-upcase-recursive', +;; `diredp-up-directory', `diredp-up-directory-reuse-dir-buffer', +;; `diredp-upcase-this-file', `diredp-verify-this-file', +;; `diredp-visit-next-file', `diredp-visit-previous-file', +;; `diredp-visit-this-file', `diredp-w32-drives', +;; `diredp-w32-drives-mode', `diredp-yank-files', +;; `global-dired-hide-details-mode' (Emacs 24.4+), +;; `toggle-diredp-find-file-reuse-dir'. +;; +;; User options defined here: +;; +;; `diredp-auto-focus-frame-for-thumbnail-tooltip-flag', +;; `diredp-bind-problematic-terminal-keys', +;; `diredp-compressed-extensions', `diredp-count-.-and-..-flag' +;; (Emacs 22+), `diredp-do-report-echo-limit', +;; `diredp-dwim-any-frame-flag' (Emacs 22+), +;; `diredp-image-preview-in-tooltip', `diff-switches', +;; `diredp-hide-details-initially-flag' (Emacs 24.4+), +;; `diredp-highlight-autofiles-mode', +;; `diredp-hide-details-propagate-flag' (Emacs 24.4+), +;; `diredp-ignore-compressed-flag', +;; `diredp-image-show-this-file-use-frame-flag' (Emacs 22+), +;; `diredp-list-file-attributes', `diredp-max-frames', +;; `diredp-move-file-dirs' (Emacs 24+), `diredp-omit-files-regexp' +;; `diredp-prompt-for-bookmark-prefix-flag', +;; `diredp-visit-ignore-extensions', `diredp-visit-ignore-regexps', +;; `diredp-w32-local-drives', `diredp-wrap-around-flag'. +;; +;; Non-interactive functions defined here: +;; +;; `derived-mode-p' (Emacs < 22), `diredp-all-files', +;; `diredp-ancestor-dirs', `diredp-apply-function-to-file-name', +;; `diredp-bookmark', +;; `diredp-create-files-non-directory-recursive', +;; `diredp-delete-dups', `diredp-delete-if', +;; `diredp-delete-if-not', `diredp-directories-within', +;; `diredp-dired-plus-description', +;; `diredp-dired-plus-description+links', +;; `diredp-dired-plus-help-link', `diredp-dired-union-1', +;; `diredp-dired-union-interactive-spec', `diredp-display-image' +;; (Emacs 22+), `diredp-do-chxxx-recursive', +;; `diredp-do-create-files-recursive', `diredp-do-grep-1', +;; `diredp-ensure-bookmark+', `diredp-ensure-mode', +;; `diredp-eval-lisp-sexp' (Emacs 22+), +;; `diredp-existing-dired-buffer-p', `diredp-fewer-than-2-files-p', +;; `diredp-fewer-than-echo-limit-files-p', +;; `diredp-fewer-than-N-files-p', `diredp-fileset-1', +;; `diredp-find-a-file-read-args', +;; `diredp-file-for-compilation-hit-at-point' (Emacs 24+), +;; `diredp-files-within', `diredp-files-within-1', +;; `diredp-fit-frame-unless-buffer-narrowed' (Emacs 24.4+), +;; `diredp-get-confirmation-recursive', `diredp-get-files', +;; `diredp-get-files-for-dir', `diredp-get-subdirs', +;; `diredp-hide-details-if-dired' (Emacs 24.4+), +;; `diredp-hide/show-details' (Emacs 24.4+), +;; `diredp-highlight-autofiles', `diredp-image-dired-required-msg', +;; `diredp-get-image-filename', `diredp-internal-do-deletions', +;; `diredp-invoke-emacs-command', `diredp-invoke-function-no-args', +;; `diredp-list-file', `diredp-list-files', `diredp-looking-at-p', +;; `diredp-make-find-file-keys-reuse-dirs', +;; `diredp-make-find-file-keys-not-reuse-dirs', `diredp-maplist', +;; `diredp-map-over-marks-and-report', `diredp-marked-here', +;; `diredp-mark-files-tagged-all/none', +;; `diredp-mark-files-tagged-some/not-all', +;; `diredp-nonempty-region-p', `diredp-parent-dir', +;; `diredp-paste-add-tags', `diredp-paste-replace-tags', +;; `diredp-read-bookmark-file-args', `diredp-read-command', +;; `diredp-read-expression' (Emacs 22+), +;; `diredp-read-include/exclude', `diredp-read-regexp', +;; `diredp-recent-dirs', `diredp-refontify-buffer', +;; `diredp-remove-if', `diredp-remove-if-not', +;; `diredp-report-file-result', `diredp--reuse-dir-buffer-helper', +;; `diredp-root-directory-p', `diredp-set-header-line-breadcrumbs' +;; (Emacs 22+), `diredp-set-tag-value', `diredp-set-union', +;; `diredp--set-up-font-locking', `diredp-string-match-p', +;; `diredp-tag', `diredp-this-file-marked-p', +;; `diredp-this-file-unmarked-p', `diredp-this-subdir', +;; `diredp-untag', `diredp-visit-ignore-regexp', +;; `diredp-y-or-n-files-p'. +;; +;; Variables defined here: +;; +;; `diredp-bookmark-menu', `diredp-file-line-overlay', +;; `diredp-files-within-dirs-done', `diredp-font-lock-keywords-1', +;; `diredp-hide-details-last-state' (Emacs 24.4+), +;; `diredp-hide-details-toggled' (Emacs 24.4+), +;; `diredp-hide/show-menu', `diredp-images-recursive-menu', +;; `diredp-last-copied-filenames', `diredp-list-files-map', +;; `diredp-loaded-p', `diredp-marks-recursive-menu', +;; `diredp-menu-bar-dir-menu', `diredp-menu-bar-marks-menu', +;; `diredp-menu-bar-multiple-menu', `diredp-menu-bar-regexp-menu', +;; `diredp-menu-bar-single-menu', `diredp-multiple-bookmarks-menu', +;; `diredp-multiple-delete-menu', `diredp-multiple-dired-menu', +;; `diredp-multiple-images-menu', +;; `diredp-multiple-encryption-menu', +;; `diredp-multiple-move-copy-link-menu', +;; `diredp-multiple-omit-menu', `diredp-multiple-recursive-menu', +;; `diredp-multiple-rename-menu', `diredp-multiple-search-menu', +;; `diredp-navigate-menu', `diredp-regexp-recursive-menu', +;; `diredp-re-no-dot', `diredp-single-bookmarks-menu', +;; `diredp-single-encryption-menu', `diredp-single-image-menu', +;; `diredp-single-move-copy-link-menu', `diredp-single-open-menu', +;; `diredp-single-rename-menu', `diredp-w32-drives-mode-map'. +;; +;; Macros defined here: +;; +;; `diredp-mark-if', `diredp-user-error', +;; `diredp-with-help-window'. +;; +;; +;; ***** NOTE: The following macros defined in `dired.el' have +;; been REDEFINED HERE: +;; +;; `dired-map-over-marks' - Treat multiple `C-u' specially. +;; +;; +;; ***** NOTE: The following functions defined in `dired.el' have +;; been REDEFINED or ADVISED HERE: +;; +;; `dired' - Handle non-positive prefix arg. +;; `dired-do-delete' - Display message to warn that marked, +;; not flagged, files will be deleted. +;; `dired-do-flagged-delete' - Display message to warn that flagged, +;; not marked, files will be deleted. +;; `dired-dwim-target-directory' - Uses `diredp-dwim-any-frame-flag'. +;; `dired-find-file' - Allow `.' and `..' (Emacs 20 only). +;; `dired-get-filename' - Test `./' and `../' (like `.', `..'). +;; `dired-get-marked-files' - Can include `.' and `..'. +;; Allow FILTER + DISTINGUISH-ONE-MARKED. +;; `dired-goto-file' - Fix Emacs bug #7126. +;; Remove `/' from dir before compare. +;; (Emacs < 24 only.) +;; `dired-hide-details-mode' - Respect new user options: +;; * `diredp-hide-details-initially-flag' +;; * `diredp-hide-details-propagate-flag' +;; (Emacs 24.4+) +;; `dired-insert-directory' - Compute WILDCARD arg for +;; `insert-directory' for individual file +;; (don't just use nil). (Emacs 23+, and +;; only for MS Windows) +;; `dired-insert-set-properties' - `mouse-face' on whole line. +;; `dired-flag-auto-save-files', `dired-mark-directories', +;; `dired-mark-executables', `dired-mark-files-containing-regexp', +;; `dired-mark-files-regexp', `dired-mark-symlinks' +;; - Use `diredp-mark-if', not `dired-mark-if'. +;; `dired-mark-files-regexp' - Add regexp to `regexp-search-ring'. +;; More matching possibilities. +;; Added optional arg LOCALP. +;; `dired-mark-pop-up' - Delete the window or frame popped up, +;; afterward, and bury its buffer. Do not +;; show a menu bar for pop-up frame. +;; `dired-other-frame' - Handle non-positive prefix arg. +;; `dired-other-window' - Handle non-positive prefix arg. +;; `dired-pop-to-buffer' - Put window point at bob (bug #12281). +;; (Emacs 22-24.1) +;; `dired-read-dir-and-switches' - Non-positive prefix arg behavior. +;; +;;; NOT YET: +;;; ;; `dired-readin-insert' - Use t as WILDCARD arg to +;;; ;; `dired-insert-directory'. (Emacs 23+, +;;; ;; and only for MS Windows) +;; +;; `dired-revert' - Reset `mode-line-process' to nil. +;; `dired-switches-escape-p' - Made compatible with Emacs 20, 21. +;; +;; +;; ***** NOTE: The following functions are included here with little +;; or no change to their definitions. They are here to +;; take advantage of the new definition of macro +;; `dired-map-over-marks': +;; +;; `dired-do-redisplay', `dired-map-over-marks-check', +;; `image-dired-dired-insert-marked-thumbs', +;; `image-dired-dired-toggle-marked-thumbs'. +;; +;; +;; ***** NOTE: The following functions defined in `dired-aux.el' have +;; been REDEFINED HERE: +;; +;; `dired-do-byte-compile', `dired-do-compress', `dired-do-load' - +;; Redisplay only if at most one file is being treated. +;; `dired-do-find-regexp', `dired-do-find-regexp-and-replace' - +;; Prefix arg lets you act on files other than those marked. +;; `dired-do-isearch', `dired-do-isearch-regexp', +;; `dired-do-query-replace-regexp', `dired-do-search' - +;; Use new `dired-get-marked-files'. +;; `dired-insert-subdir-newpos' - If not a descendant, put at eob. +;; `dired-insert-subdir-validate' - Do nothing: no restrictions. +;; `dired-maybe-insert-subdir' - Go back to subdir line if in listing. +;; `dired-handle-overwrite' - Added optional arg FROM, for listing. +;; `dired-copy-file(-recursive)', `dired-hardlink', `dired-query', +;; `dired-rename-file' - You can list (`l') the files involved. +;; +;; +;; ***** NOTE: The following functions defined in `dired-x.el' have +;; been REDEFINED HERE: +;; +;; `dired-copy-filename-as-kill' - +;; Put file names also in var `diredp-last-copied-filenames'. +;; `dired-do-find-marked-files' - +;; Call `dired-get-marked-files' with original ARG. +;; Added optional arg INTERACTIVEP - no error if nil and no files. +;; `dired-do-run-mail' - Require confirmation. +;; `dired-mark-sexp' - 1. Variable `s' -> `blks'. +;; 2. Fixes to `uid' and `gid'. +;; `dired-mark-unmarked-files' (Emacs < 24 only) - Emacs 24+ version. +;; `dired-simultaneous-find-file' - +;; Use separate frames instead of windows if `pop-up-frames' is +;; non-nil, or if prefix arg < 0. +;; +;; +;; ***** NOTE: (Emacs 20 only) The following variable defined in +;; `dired.el' has been REDEFINED HERE: +;; +;; `dired-move-to-filename-regexp' - Recognize file size in k etc. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Change Log: +;; +;; 2019/07/03 dadams +;; dired-mark-unmarked-files: Apply fix for Emacs bug #27465. +;; diredp-mark-if, diredp-mark-sexp(-recursive), dired-mark-unmarked-files: +;; Use char-after, not diredp-looking-at-p. +;; 2019/07/19 dadams +;; diredp-change-marks-recursive, diredp-unmark-all-files-recursive, +;; diredp-mark-files(-containing)-regexp-recursive, diredp-mark-sexp-recursive, diredp-mark-recursive-1: +;; Added missing PREDICATE arg in calls to diredp-get-subdirs. +;; 2019/06/25 dadams +;; diredp-mark-if, diredp-this-file-(un)marked-p: Use regexp-quote for marker char. +;; 2019/06/03 dadams +;; Removed autoload cookie for diredp-omit-files-regexp - it evaluates dired-omit-files, from dired-x.el. +;; Hard-require dired-x.el. (No reason not to.) Removed fboundp guards for it. +;; 2019/04/22 dadams +;; Added diredp-move-files-named-in-kill-ring. Bound to C-w. +;; 2019/04/21 dadams +;; Added redefinitions of dired-do-find-regexp, dired-do-find-regexp-and-replace. +;; diredp-multiple-search-menu: Added "Using TAGS Table" for dired-do-(query-replace|search). +;; 2019/04/20 dadams +;; Added: +;; diredp-map-over-marks-and-report, diredp-do-emacs-command, diredp-invoke-emacs-command, +;; diredp-read-command, diredp-do-lisp-sexp, diredp-eval-lisp-sexp, diredp-report-file-result, +;; diredp-do-report-echo-limit, diredp-fewer-than-N-files-p, diredp-fewer-than-echo-limit-files-p, +;; diredp-apply-function-to-file-name, diredp-invoke-function-no-args, diredp-list-file-attributes. +;; diredp-do-apply-function: Redefine to use diredp-map-over-marks-and-report. +;; diredp-dired-plus-description, diredp-menu-bar-multiple-menu: +;; Added diredp-do-emacs-command, diredp-do-lisp-sexp. +;; diredp-menu-bar-multiple-menu: Reordered items. +;; diredp-list-marked, diredp-*-recursive, diredp-describe-marked-autofiles: +;; Use diredp-list-file-attributes for DETAILS arg interactively. +;; diredp-yank-files, dired-query: Use diredp-list-file-attributes, not harcoded list (5 8). +;; diredp-set-bookmark-file-bookmark-for-marked-recursive: Corrected interactive spec. +;; 2019/04/16 dadams +;; Added: diredp-delete-if. +;; dired-map-over-marks-check: Added &rest argument FUN-ARGS, so FUN can accept arguments. +;; 2019/04/12 dadams +;; dired-get-marked-files: Do not add t to RESULT. Thx to Jeff Spencer for bug report. +;; If all marked is (t) for some reason reset it to nil, per vanilla Emacs 24+. +;; diredp-compressed-extensions: Added .rar, .rev. +;; 2019/04/10 dadams +;; Added diredp-read-expression (forgot it when added diredp-mark-sexp-recursive). +;; diredp-mark-sexp-recursive is thus only for Emacs 22+. +;; 2019/03/20 dadams +;; Added option diredp-omit-files-regexp. +;; Face diredp-omit-file-name: Added strike-through. +;; diredp-font-lock-keywords-1, for face diredp-omit-file-name: +;; Move to file name. Use diredp-omit-files-regexp. Append * for executable flag. Highlight whole line. +;; 2019/03/17 dadams +;; diredp-font-lock-keywords-1: +;; Use just dired-omit-files as regexp - its components already have ^...$. +;; Removed superfluous execute *'s in regexps and superfluous concat for compressed extensions. +;; Face diredp-omit-file-name: Removed :strike-through for default value. +;; 2019/03/16 dadms +;; Added face diredp-omit-file-name. +;; diredp-font-lock-keywords-1: Use face diredp-omit-file-name for dired-omit-files matches. +;; 2019/03/15 dadams +;; diredp-font-lock-keywords-1: Treat dired-omit-files like dired-omit-extensions. +;; 2019/01/27 dadams +;; Added: diredp-mark-files-containing-regexp-recursive. +;; Bound to M-+ % g. Added to diredp-marks-recursive-menu, diredp-regexp-recursive-menu. +;; 2019/01/17 dadams +;; Added: diredp-mark-sexp-recursive. Bound to M-+ M-(, M-+ * (. Added to diredp-marks-recursive-menu. +;; dired-query: Use dired-query-alist only when available. +;; diredp-move-file: Fix format string in error call. +;; diredp-mark-symlinks-recursive: Added missing DETAILS arg for diredp-mark-recursive-1. +;; 2019/01/01 dadams +;; Added: diredp-list-file. +;; Added redefinitions of dired-query, dired-handle-overwrite, dired-copy-file(-recursive), dired-rename-file, +;; dired-hardlink. +;; Added optional arg DETAILS to these functions: diredp-get-(subdirs|files), diredp-y-or-n-files-p, +;; diredp-list-(marked|files), diredp-yank-files, diredp-describe-marked-autofiles, plus all functions with +;; "recursive" in their name except diredp-get-confirmation-recursive. +;; Added optional arg DETAILS. +;; diredp-get-(subdirs|files), diredp-y-or-n-files-p, diredp-list-(marked|files), diredp-yank-files, +;; diredp-describe-marked-autofiles: +;; Added optional arg DETAILS. +;; diredp-list-files: Use dired-list-file, to optionally show details. +;; diredp-yank-files: Non-positive prefix arg shows details now. +;; 2018/12/02 dadams +;; dired-mark-pop-up: Work around Emacs 22 bug in dired-pop-to-buffer which can exit in Dired buffer. +;; 2018/10/17 dadams +;; dired-read-dir-and-switches: Removed mention of icicle-file-sort-first-time-p (no longer used in Icicles). +;; 2018/09/21 dadams +;; diredp-image-dired-edit-comment-and-tags, diredp-w32-drives: +;; Use pop-to-buffer-same-window, not switch-to-buffer. +;; 2018/09/14 dadams +;; Added: diredp-move-file-dirs, diredp-move-file. +;; 2018/06/30 dadams +;; Added: diredp-delete-if-not. +;; 2018/06/16 dadams +;; Added: diredp-visit-ignore-extensions, diredp-visit-ignore-regexps, diredp-visit-next-file, +;; diredp-visit-previous-file, diredp-visit-this-file, diredp-visit-ignore-regexp. +;; Bind the commands to C-down, C-up, e. +;; 2018/03/25 dadams +;; Added: diredp-user-error. +;; Updated for Emacs 27-pretest-2 change in dired-get-marked-files signature. +;; dired-get-marked-files: Added optional arg ERROR-IF-NONE-P. +;; diredp-list-marked, diredp-insert-subdirs, dired-do-(i)search(-regexp), dired-do-query-replace-regexp, +;; dired-do-find-marked-files, diredp-describe-marked-autofiles: +;; Added optional arg INTERACTIVEP. +;; Pass non-nil ERROR-IF-NONE-P to dired-get-marked-files when INTERACTIVEP. (See Emacs bug #30938.) +;; 2018/03/23 dadams +;; Added diredp-mark-if. Removed: redefinition of dired-mark-if. +;; Differences: msg and return value include both number of matches and number of changes. +;; Added redefinitions (use diredp-mark-if) of dired-flag-auto-save-files, +;; dired-mark-(files-containing-regexp|symlinks|directories|executables). +;; Everywhere: Use diredp-mark-if, not dired-mark-if. +;; 2018/03/03 dadams +;; diredp-delete-dups: defalias the symbol, not its symbol-function (dunno why I did the latter). +;; 2018/02/28 dadams +;; Added: diredp-last-copied-filenames, diredp-copy-abs-filenames-as-kill-recursive, +;; and redefinition of vanilla diredp-last-copied-filenames. +;; diredp-copy-abs-filenames-as-kill: Use diredp-ensure-mode in interactive spec. +;; diredp-copy-filename-as-kill-recursive: Update diredp-last-copied-filenames with filenames string. +;; diredp-yank-files: Require confirmation for pasting, using diredp-y-or-n-files-p. +;; Get file names from variable diredp-last-copied-filenames, not kill-ring. +;; Added NO-CONFIRM-P arg. +;; diredp-ensure-mode: Added doc string. +;; diredp-do-grep, diredp-do-grep-recursive: Changed bindings to C-M-G and M-+ C-M-G, due to M-g conflict. +;; 2018/02/27 dadams +;; Added: diredp-copy-abs-filenames-as-kill, diredp-yank-files (aka diredp-paste-files) (bound to C-y). +;; diredp-menu-bar-multiple-menu: Added diredp-copy-abs-filenames-as-kill. +;; diredp-menu-bar-dir-menu: Added diredp-yank-files. +;; 2018/01/11 dadams +;; diredp-get-files: +;; Set IGNORE-MARKS-P to non-nil if nothing marked here. (It was not getting all if nothing marked.) +;; diredp-marked-recursive(-other-window): +;; Corrected interactive spec, which was missing nil DIRNAME arg. Corrected body: use DIRNAME. +;; diredp-get-files-for-dir, diredp-do-bookmark-dirs-recursive, diredp-change-marks-recursive, +;; diredp-unmark-all-files-recursive, diredp-mark-files-regexp-recursive, diredp-mark-recursive-1, +;; diredp-do-delete-recursive: +;; Factor out (dired-buffers-for-dir (expand-file-name directory)). +;; 2018/01/03 dadams +;; dired-mark-files-regexp: Corrected doc string wrt prefix args. Thx to John Mastro. +;; diredp-do-grep-recursive: Removed unused optional arg IGNORE-MARKS-P. +;; diredp-marked-recursive(-other-window): Moved handling of optional arg from interactive spec to body. +;; 2018/01/02 dadams +;; Added: diredp-flag-auto-save-files-recursive. Bound to M-+ #. +;; diredp-get-file-or-dir-name, diredp-marked-here: Doubled backslashes to escape dots. +;; diredp-marked-here: Fixed regexp to match only double-dot, not single-dot. +;; diredp-flag-auto-save-files-recursive: Updated to include more M-+ keys. +;; diredp-marks-recursive-menu: Added diredp-flag-auto-save-files-recursive. +;; 2017/12/31 dadams +;; diredp-get-files-for-dir: Pass non-nil NO-DOT-DOT-P arg to diredp-marked-here. +;; dired-get-marked-files: Allow use of FILTER and DISTINGUISH-ONE-MARKED together. +;; diredp-marked-here: Added optional arg NO-DOT-DOT-P. +;; diredp-change-marks-recursive, diredp-unmark-all-files-recursive: Removed unused vars include-dirs, files. +;; 2017/12/30 dadams +;; Added: diredp-change-marks-recursive, diredp-unmark-all-files-recursive, diredp-unmark-all-marks-recursive. +;; Bound to M-+ * c, M-+ M-DEL, M-+ U, respectively. +;; diredp-menu-bar-marks-menu: Rename item Change Marks to Change Mark. +;; diredp-marks-recursive-menu, diredp-multiple-recursive-menu: +;; Added diredp-change-marks-recursive, diredp-unmark-all-(files|marks)-recursive. +;; 2017/12/21 dadams +;; Added: diredp-mark-recursive-1. Forgot to add it last June. +;; 2017/12/17 dadams +;; Removed: diredp-display-graphic-p. +;; Do not use diredp-display-graphic-p to allow binding diredp-bind-problematic-terminal-keys by default. +;; 2017/11/25 dadams +;; diredp-nb-marked-in-mode-name: Wrap last :eval sexp in save-excursion. +;; Protect Call dired-current-directory only when dired-subdir-alist. +;; 2017/10/23 dadams +;; Added: diredp-count-.-and-..-flag, diredp--reuse-dir-buffer-helper. +;; Removed: diredp-mouse-find-file. +;; diredp-find-file-reuse-dir-buffer, diredp-mouse-find-file-reuse-dir-buffer, +;; diredp-up-directory-reuse-dir-buffer: +;; Use diredp--reuse-dir-buffer-helper. +;; diredp-find-file-reuse-dir-buffer: Changed logic: do find-alternate-file only if target is a dir not in +;; Dired and current Dired buffer is in only this window. +;; diredp-mouse-find-file-reuse-dir-buffer: Added optional args FIND-FILE-FUNC and FIND-DIR-FUNC. +;; diredp-up-directory, diredp-up-directory-reuse-dir-buffer: Pass OTHER-WINDOW arg to diredp-w32-drives. +;; diredp-nb-marked-in-mode-name: Show also number of lines in current listing, and listing-relative lineno, +;; respecting diredp-count-.-and-..-flag. +;; diredp-find-a-file*: Added autoload cookies. +;; 2017/08/18 dadams +;; Fixed emacswiki URLs everywhere. They changed the locations and changed http to https. +;; 2017/06/30 dadams +;; Added: diredp-bind-problematic-terminal-keys, diredp-display-graphic-p. +;; Guard bindings of problematic keys with diredp-display-graphic-p & diredp-bind-problematic-terminal-keys. +;; Documented problematic keys for terminal mode in commentary. +;; 2017/06/23 dadams +;; Added: diredp-read-regexp (removed alias to read-regexp), diredp-marks-recursive-menu, +;; diredp-mark-executables-recursive (bound to M-+ * *), +;; diredp-mark-directories-recursive (bound to M-+ * /), +;; diredp-mark-extension-recursive (bound to M-+ * .), +;; diredp-mark-autofiles-recursive (bound to M-+ * B), +;; diredp-mark-executables-recursive (bound to M-+ * *), +;; diredp-mark-directories-recursive (bound to M-+ * /), +;; diredp-mark-symlinks-recursive (bound to M-+ * @), +;; Bind diredp-mark-autofiles to * B. +;; diredp-marked-here: Bind dired-marker-char to ?*. +;; diredp-mark-files-regexp-recursive: Better msgs - show total count. +;; Everywhere: Use diredp-looking-at, not looking-at. Use diredp-read-regexp, not dired-read-regexp. +;; 2017/05/30 dadams +;; Fixed typo: direp--set-up-font-locking -> diredp--set-up-font-locking. +;; 2017/05/22 dadams +;; Added: direp--set-up-font-locking. +;; Use direp--set-up-font-locking instead of lambda in dired-mode-hook. +;; 2017/04/09 dadams +;; Version 2017.04.09. +;; Added: diredp-multiple-move-copy-link-menu, diredp-multiple-rename-menu, diredp-multiple-dired-menu, +;; diredp-multiple-omit-menu, diredp-multiple-delete-menu, diredp-single-bookmarks-menu, +;; diredp-single-encryption-menu, diredp-single-image-menu, diredp-single-open-menu, +;; diredp-single-move-copy-link-menu, diredp-single-rename-menu. +;; Moved single menu items there. +;; Renamed: diredp-menu-bar-encryption-menu to diredp-multiple-encryption-menu, +;; diredp-menu-bar-mark-menu to diredp-menu-bar-marks-menu, +;; diredp-menu-bar-operate-menu to diredp-menu-bar-multiple-menu, +;; diredp-menu-bar-operate-bookmarks-menu to diredp-multiple-bookmarks-menu, +;; diredp-menu-bar-operate-recursive-menu to diredp-multiple-recursive-menu, +;; diredp-menu-bar-operate-search-menu to diredp-multiple-search-menu, +;; diredp-menu-bar-images-menu to diredp-multiple-images-menu, +;; diredp-menu-bar-images-recursive-menu to diredp-images-recursive-menu, +;; diredp-menu-bar-immediate-menu to diredp-menu-bar-single-menu, +;; diredp-menu-bar-regexp-recursive-menu to diredp-regexp-recursive-menu, +;; diredp-menu-bar-subdir-menu to diredp-menu-bar-dir-menu. +;; Added dired-do-rename to diredp-multiple-rename-menu. +;; diredp-nonempty-region-p: Ensure (mark) also. +;; 2017/03/30 dadams +;; Moved key bindings to end of file. Moved defgroup before defcustoms. +;; Bind dired-multiple-w32-browser to C-M-RET, diredp-multiple-w32-browser-recursive to M-+ C-M-RET. +;; 2017/03/29 dadams +;; Added: diredp-dired-union-other-window, diredp-add-to-dired-buffer-other-window. +;; diredp-dired-union-1: Added optional arg OTHERWIN. +;; diredp-dired-plus-description: Updated doc string. +;; diredp-menu-bar-subdir-menu: Added diredp-dired-for-files. +;; Bind diredp-w32-drives to :/, diredp-dired-inserted-subdirs to C-M-i. +;; Bind diredp-add-to-dired-buffer to C-x D A (not C-x E), diredp-dired-union to C-x D U (not C-x D), +;; diredp-fileset to C-x D S (not C-M-f), diredp-dired-recent-dirs to C-x D R (not C-x R), +;; diredp-dired-for-files to C-x D F, plus other-window versions. +;; 2017/03/24 dadams +;; Added defalias for dired-read-regexp. +;; diredp-mouse-3-menu: Removed second arg to mouse-save-then-kill. +;; 2017/02/20 dadams +;; diredp-(next|previous)-line, diredp-(next|prev)-dirline, diredp-(next|prev)-subdir: +;; Update interactive spec to use (in effect) ^p for prefix arg (for shift-select-mode). +;; 2017/01/12 dadams +;; dired-mark-files-regexp: Swapped prefix-arg behavior for relative and absolute name matching. +;; 2017/01/01 dadams +;; dired-mark-files-regexp: Fix to prompt for no prefix arg. +;; 2016/12/28 dadams +;; dired-mark-files-regexp: Corrected prompt string for Mark/UNmark. Thx to Tino Calancha. +;; 2016/11/20 dadams +;; diredp-menu-bar-operate-search-menu: Added dired-do-find-regexp and dired-do-find-regexp-and-replace. +;; Bind dired-do-search to M-a and dired-do-query(-replace)-regexp to M-q. +;; diredp-dired-plus-description: Added dired-do-find-regexp and dired-do-find-regexp-and-replace. +;; 2016/10/12 dadams +;; diredp-compressed-extensions: Added extensions .xz and .lzma. Thx to xuhdev (https://www.topbug.net/). +;; 2016/09/20 dadams +;; Emacs 25.1: Bind M-z to dired-do-compress-to (replaces c). (Emacs bug #24484.) +;; diredp-menu-bar-operate-menu: Added item: Compress to (dired-do-compress-to). +;; 2016/09/15 dadams +;; Added: diredp-max-frames. +;; dired-do-find-marked-files: Pass non-nil ARG to dired-get-marked-files only if it is a cons. +;; Clarified doc string wrt prefix arg. +;; dired-simultaneous-find-file: Require confirmation if more files than diredp-max-frames. +;; diredp-do-find-marked-files-recursive: Clarified doc string wrt prefix arg. +;; Thx to Tino Calancha. +;; 2016/09/14 dadams +;; diredp-dired-plus-description: Added entry for dired-hide-details-mode - ( key. +;; 2016/08/26 dadams +;; diredp-y-or-n-files-p: pop-to-buffer only when the buffer was created. +;; Update wrt vanilla (scroll actions). +;; diredp-do-query-replace-regexp-recursive: +;; Call diredp-get-confirmation-recursive. +;; Use only diredp-get-files, not dired-get-marked-files. +;; Non-positive prefix arg means DELIMITED. +;; 2016/08/08 dadams +;; diredp-menu-bar-mark-menu: +;; Added: dired-mark-files-containing-regexp, dired-mark-sexp, image-dired-mark-tagged-files, +;; 2016/05/28 dadams +;; diredp-mark-files-regexp-recursive: Use nil for dired-get-filename LOCALP arg. +;; dired-mark-files-regexp: Corrected doc string: absolute filename matching by default. +;; 2016/05/24 dadams +;; dired-mark-files-regexp: Added optional arg LOCALP, so can mark/unmark matching different file-name forms. +;; 2016/05/15 dadams +;; Added: diredp-bookmark-menu, diredp-hide/show-menu, diredp-navigate-menu. +;; Move insert after revert and rename it to Insert/Move-To This Subdir. Move create-directory before revert. +;; 2016/04/29 dadams +;; diredp-next-line: Respect goal-column. +;; 2016/01/24 dadams +;; Added: diredp-ensure-bookmark+, diredp-mark-autofiles, diredp-unmark-autofiles, +;; diredp-mark/unmark-autofiles, diredp-describe-autofile, diredp-show-metadata, +;; diredp-mouse-describe-autofile, diredp-describe-marked-autofiles, diredp-show-metadata-for-marked +;; Soft-require help-fns+.el (Emacs 22+) or help+20.el (Emacs 20-21). +;; Add to menu-bar menus: +;; diredp-(un)mark-autofiles, diredp-describe-autofile, diredp-describe-marked-autofiles. +;; diredp-menu-bar-immediate-menu: Add diredp-describe-file only if defined. +;; Bind diredp-describe-file to keys only if defined. +;; Use diredp-ensure-bookmark+ everywhere, instead of its definition. +;; diredp(-mouse)-describe-file: Define only if describe-file is defined. Removed raising error if not. +;; diredp-mouse-3-menu: Use diredp-describe-autofile if diredp-describe-file is not defined. +;; diredp-dired-plus-description: Add diredp-mouse-describe-autofile, when bound. +;; dired-mark-if: Do not count non-changes. +;; 2015/12/15 dadams +;; diredp-font-lock-keywords-1: Follow # with optional [/ ], for face diredp-number. Thx to Tino Calancha. +;; 2015/11/10 dadams +;; diredp-fileset(-other-window): Separate error msgs for unloaded filesets.el and empty filesets-data. +;; 2015/10/02 dadams +;; dired-mark-sexp: Like vanilla, skip extended attributes marker before setting NLINK. Thx to Tino Calancha. +;; 2015/09/29 dadams +;; diredp-delete-this-file: Redefined to use delete-file instead of dired-do-delete. +;; 2015/09/07 dadams +;; diredp-font-lock-keywords-1: Do not test diredp-ignore-compressed-flag when highlighting file names. +;; Use separate entries for compressed and non-compressed file names. +;; Added missing \\| before ignored compressed extensions. +;; 2015/09/06 dadams +;; diredp-compressed-extensions: Added .tgz. Removed duplicate .gz. +;; diredp-font-lock-keywords-1: Use regexp-opt where possible, instead of mapcar regexp-quote. +;; 2015/09/05 dadams +;; Added: diredp-compressed-extensions, diredp-ignore-compressed-flag, diredp-compressed-file-name, +;; diredp-dir-name. +;; diredp-font-lock-keywords-1: +;; Allow spaces in symlink names. Highlight compressed-file names, if diredp-ignore-compressed-flag. +;; Use diredp-compressed-extensions instead of hardcoding extensions. +;; Highlight d with diredp-dir-priv (fix). +;; Treat l in third column the same as - and d there. +;; Highlight whole line for D-flagged files, with face diredp-deletion-file-name. +;; Thx to Nick Helm. +;; 2015/08/30 dadams +;; dired-mark-sexp: Updated per Emacs 25 code. +;; 2015/07/30 dadams +;; diredp-fileset(-other-window): Changed key binding from C-x F to C-x C-M-f (conflicted with find-function). +;; 2015/06/24 dadams +;; Added: diredp-parent-dir, diredp-breadcrumbs-in-header-line-mode, diredp-set-header-line-breadcrumbs. +;; 2015/06/06 dadams +;; Added dired-other-(frame|window). +;; diredp-font-lock-keywords-1: +;; Use dired-re-maybe-mark and dired-re-inode-size for permission matchings and directory names. +;; dired(-other-(frame|window)) advice: +;; Add interactive spec, to handle arg <= 0 (broken by change to dired-read-dir-and-switches 2015/02/02). +;; diredp-dired-for-files: Typo: pass empy string. +;; 2015/06/05 dadams +;; Added: diredp-grepped-files-other-window as alias for diredp-compilation-files-other-window. +;; diredp-compilation-files-other-window: Added SWITCHES optional arg (prefix arg). +;; 2015/06/04 dadams +;; diredp-dired-for-files(-other-window): +;; Updated to fit change to dired-read-dir-and-switches made 2015/02/02: addition of READ-EXTRA-FILES-P. +;; Use prefix arg to prompt for switches. +;; 2015/05/31 dadams +;; Added: diredp-image-show-this-file,diredp-image-show-this-file-use-frame-flag, diredp-get-image-filename. +;; image-dired-dired-toggle-marked-thumbs, diredp-menu-bar-immediate-menu [image]: +;; Use diredp-get-image-filename. +;; Bound diredp-image-show-this-file to C-t I. +;; diredp-menu-bar-immediate-image-menu: Added diredp-image-show-this-file and dired-find-file. +;; Added autoload cookies for image commands. +;; 2015/04/16 dadams +;; Added: diredp-do-apply-function, diredp-do-apply-function-recursive. Added to menus. Bind to @, M-+ @. +;; dired-do-query-replace-regexp: Handle nil ARG properly. +;; 2015/03/26 dadams +;; Added: redefinitions of dired-do-isearch, dired-do-isearch-regexp, dired-do-query-replace-regexp, +;; dired-do-search, to handle multi-C-u. +;; Added: dired-nondirectory-p (Emacs 20), diredp-refontify-buffer. +;; dired-do-byte-compile, dired-do-load, : Corrected interactive spec, to treat more than two C-u as two. +;; dired-after-readin-hook: Add diredp-refontify-buffer. In particular, this ensures that reverting Dired +;; for a listing of explicit file names gets refontified. (Just turn-on-font-lock does not refontify.) +;; 2015/03/24 dadams +;; Added: diredp-compilation-files-other-window, diredp-file-for-compilation-hit-at-point. +;; 2015/03/06 dadams +;; Renamed: diredp-menu-bar-recursive-marked-menu to diredp-menu-bar-operate-recursive-menu. +;; Added: diredp-do-delete-recursive: M-+ D. Added to diredp-menu-bar-operate-recursive-menu. +;; Added: diredp-mark-files-regexp-recursive: M-+ % m. Added to diredp-menu-bar-regexp-recursive-menu. +;; 2015/03/04 dadams +;; Added: diredp-dwim-any-frame-flag, (redefinition of) dired-dwim-target-directory. +;; 2015/02/22 dadams +;; diredp-bookmark: Corrected for use without Bookmark+ - bookmark-store signature. +;; Pass correct value to bmkp-autofile-set for its MSG-P arg. +;; diredp-mouse-do-bookmark: Do not pass non-nil NO-MSG-P arg to diredp-bookmark. +;; 2015/02/03 dadams +;; Added: diredp-add-to-this-dired-buffer. +;; Removed: diredp-add-to-dired-buffer-other-window, diredp-dired-union-other-window. +;; diredp-dired-union-1: Removed optional arg OTHER-WINDOW-P. +;; diredp-menu-bar-subdir-menu: Added diredp-add-to-this-dired-buffer. +;; dired-read-dir-and-switches, diredp-dired-union-interactive-spec: +;; Added optional arg DIRED-BUFFER. If nil, use current buffer name as default when reading buffer name. +;; 2015/02/02 dadams +;; Added: diredp-add-to-dired-buffer, diredp-add-to-dired-buffer-other-window, diredp-set-union, +;; diredp-existing-dired-buffer-p. +;; Bind diredp-add-to-dired-buffer(-other-window) globally to C-x E, C-x 4 E. +;; diredp-dired-union(-other-window): +;; Added args DIRNAME and EXTRA. Pass them to diredp-dired-union-1. Moved "UNION" to *-interactive-spec. +;; Pass values for new args NO-DIRED-BUFS and READ-EXTRA-FILES-P to diredp-dired-union-interactive-spec. +;; diredp-dired-union-interactive-spec: +;; Added args NO-DIRED-BUFS and READ-EXTRA-FILES-P. Use (updated) dired-read-dir-and-switches. +;; Delete dead buffers from dired-buffers. Remove DIRNAME buffer as candidate. +;; Apply expand-file-name to default-directory. Return list of DIRNAME BUFS SWITCHES EXTRA-FILES. +;; diredp-dired-union-1: +;; Added args DIRED-NAME and EXTRA. +;; For existing Dired buffer whose dired-directory is a cons: +;; Include its current listing. Replace buffer with new one of same name, after deleting its window. +;; dired-read-dir-and-switches: +;; Added arg READ-EXTRA-FILES-P. +;; If chosen Dired buffer exists and is an ordinary listing then start out with its directory-files. +;; diredp-dired-union, diredp-fileset, diredp-dired-recent-dirs: Bind globally, not just in Dired mode. +;; 2015/01/30 dadams +;; dired-read-dir-and-switches: Remove any killed buffers from dired-buffers, before using for completion. +;; 2014/10/25 dadams +;; diredp-dired-union-interactive-spec: Typo: quote buffer-name-history. Pass other-window STRING. +;; diredp-dired-union-other-window: Pass other-window STRING. +;; dired-read-dir-and-switches: Include STRING for reading buffer name also. +;; dired (defadvice): Corrected doc string for prefix arg >= and <= 0. +;; 2014/10/15 dadams +;; diredp-hide-details-initially-flag: +;; Added :set, to ensure that diredp-hide-details-last-state is kept up-to-date. +;; 2014/09/28 dadams +;; Added: diredp-recent-dirs, diredp-read-include/exclude, diredp-root-directory-p, diredp-remove-if. +;; diredp-dired-recent-dirs(-other-window): Added optional ARG. Use diredp-recent-dirs. Pass SWITCHES. +;; dired-read-dir-and-switches: Use diredp-root-directory-p. +;; Bound diredp-dired-recent-dirs(-other-window) to C-x R and C-x 4 R. +;; Added diredp-dired-recent-dirs to Dir menu. +;; 2014/09/27 dadams +;; Added: diredp-dired-recent-dirs, diredp-dired-recent-dirs-other-window, diredp-delete-dups. +;; 2014/09/26 dadams +;; diredp-mouseover-help: dired-get-filename etc. has to be inside the save-excursion. +;; diredp-image-dired-create-thumb: Added FILE arg. Use numeric prefix arg as the new thumbnail size. +;; 2014/09/22 dadams +;; diredp-mouse-3-menu: Do not place overlay unless on a file/dir name (i.e., dired-get-filename). +;; 2014/09/15 dadams +;; dired-read-dir-and-switches: Made it (thus dired too) an Icicles multi-command. +;; dired (defadvice): Added doc about using it with Icicles. +;; 2014/09/14 dadams +;; Added: diredp-kill-this-tree. +;; Removed: diredp-dired-files(-other-window), diredp-dired-files-interactive-spec. +;; dired-read-dir-and-switches: +;; Based on diredp-dired-files-interactive-spec implementation now, but: +;; Moved unwind-protect outside call to list. completing-read, not read-string, for DIRBUF. +;; Do not allow inclusion of root directories. Protected icicle-sort-comparer with boundp. +;; dired-insert-subdir-validate: Make it a no-op. +;; dired advice (doc string): Mention wildcards, Icicles. +;; diredp-dired-for-files(-other-window): +;; Use dired-read-dir-and-switches and dired, not diredp-dired-files-interactive-spec and +;; diredp-dired-files. +;; diredp-menu-bar-immediate-menu, diredp-mouse-3-menu: +;; Added item for diredp-kill-this-tree. +;; Corrected visible condition: expand-file-name, so ~/ compares with its expansion. +;; diredp-font-lock-keywords-1: Include period (.) for diredp(-compressed)-file-suffix. +;; 2014/09/09 dadams +;; Added: dired-read-dir-and-switches. +;; Advise dired, for doc string. +;; dired-get-filename: Hack for Emacs 20-22, to expand ~/... +;; 2014/09/07 dadams +;; Added: redefinitions of dired-insert-subdir-newpos, dired-insert-subdir-validate. +;; 2014/07/26 dadams +;; diredp-do-find-marked-files-recursive: +;; Only ARG >= 0 ignores marks now. And ARG <= 0 means find but do not display. +;; 2014/07/13 dadams +;; diredp-mouseover-help: Wrap (goto-char pos) in save-excursion (Emacs bug #18011). +;; 2014/07/12 dadams +;; Faces diredp(-tagged)-autofile-name: Made paler/darker (less saturated). +;; Moved diredp-highlight-autofiles before diredp-highlight-autofiles-mode, so will be +;; defined for first revert. +;; diredp-mouse-3-menu: Renamed items Tag, Untag to Add Tags, Remove Tags. +;; diredp-dired-plus-description: Updated. +;; 2014/07/11 dadams +;; Added: diredp-highlight-autofiles-mode, diredp-highlight-autofiles, +;; diredp-autofile-name, diredp-tagged-autofile-name. +;; Soft-require bookmark+.el. Soft-require highlight.el if bookmark+.el is provided. +;; diredp-menu-bar-subdir-menu: Added item Toggle Autofile Highlighting. +;; Removed unused face: diredp-display-msg. +;; 2014/06/29 dadams +;; dired-get-marked-files, diredp-internal-do-deletions: +;; Remove nils from dired-map-over-marks result. +;; 2014/05/28 dadams +;; diredp-mode-line-marked: Use DarkViolet for both light and dark background modes. +;; 2014/05/23 dadams +;; Added: diredp-with-help-window. +;; diredp-list-files, diredp-dired-plus-help: +;; Use diredp-with-help-window, not with-output-to-temp-buffer. See Emacs bug #17109. +;; 2014/05/06 dadams +;; Added: diredp-image-dired-required-msg, diredp-list-files-map, +;; diredp-find-line-file-other-window, diredp-mouse-find-line-file-other-window, +;; image-dired-dired-toggle-marked-thumbs, diredp-list-marked. +;; Soft-require image-dired.el and image-file.el. +;; diredp-image-dired-create-thumb: Define unconditionally. +;; image-dired-dired-insert-marked-thumbs, diredp-image-dired-comment-file, +;; diredp-image-dired-tag-file, diredp-image-dired-delete-tag, +;; diredp-image-dired-display-thumb, diredp-image-dired-copy-with-exif-name, +;; diredp-image-dired-edit-comment-and-tags, diredp-do-display-images: +;; Define unconditionally and raise error if no image-(dired|file).el. +;; diredp-menu-bar-immediate-image-menu, diredp-menu-bar-images-menu, +;; diredp-menu-bar-images-recursive-menu, image-dired-mark-tagged-files: +;; Define unconditionally and use :enable. +;; diredp-menu-bar-images-menu, diredp-menu-bar-images-recursive-menu: +;; Add defalias so can use menu-item with :enable. +;; diredp-list-files: Add properties mouse-face, keymap, and help-echo. +;; diredp-mouseover-help: Make it work also for diredp-list-files listings. +;; image-dired-dired-insert-marked-thumbs: Add autoload cookie. +;; dired-get-marked-files: Pass non-nil 2nd arg to dired-get-filename, to include . and .. . +;; Bind diredp-list-marked to C-M-l and diredp-list-marked-recursive to M+ C-M-l. +;; diredp-insert-subdirs: Exclude . and .., as dired-get-marked-files can now include them. +;; diredp-menu-bar-operate-menu: Add diredp-menu-bar-operate-menu to menu. +;; diredp-dired-plus-description: Mention diredp-list-marked*. +;; 2014/05/03 dadams +;; dired-switches-escape-p: Use dired-switches-check if available, based on bug #17218 fix. +;; 2014/04/25 dadams +;; diredp-image-dired-create-thumb: +;; Do not call diredp-image-dired-create-thumb twice: reuse THUMB-NAME. +;; 2014/04/24 dadams +;; Added: diredp-mouseover-help, diredp-auto-focus-frame-for-thumbnail-tooltip-flag, +;; diredp-image-preview-in-tooltip. +;; dired-insert-set-properties: Show image-file preview in tooltip. +;; diredp-image-dired-create-thumb: Return thumbnail file name or nil. +;; 2014/04/23 dadams +;; Added: diredp-looking-at-p. +;; dired-insert-set-properties: Applied fix for bug #17228. +;; 2014/04/05 dadams +;; Added: diredp-do-bookmark-dirs-recursive. +;; Renamed from bmkp-create-dired-bookmarks-recursive in bookmark+-1.el (removed). +;; Bound to M-B (aka M-S-b). +;; Added to menus *-subdir-menu, *-operate-bookmarks-menu, *-bookmarks-menu. +;; diredp-get-confirmation-recursive: Added optional TYPE arg. +;; diredp-insert-subdirs-recursive: Call diredp-get-confirmation-recursive with TYPE arg. +;; 2014/02/16 dadams +;; dired-pop-to-buffer: Do not redefine for Emacs > 24.1. +;; dired-mark-pop-up: Updated doc string. +;; 2014/02/13 dadams +;; Added: diredp-fileset-other-window, diredp-fileset-1. +;; diredp-fileset: Use diredp-fileset-1. +;; Bind diredp-dired-union(-other-window) to C-x D, C-x 4 D, +;; diredp-fileset(-other-window) to C-x F, C-x 4 F. +;; Use diredp-fileset-other-window, not diredp-fileset, in menu. +;; 2014/02/03 dadams +;; Added: diredp-hide-subdir-nomove. +;; Added: dired-goto-file for Emacs 24+ - open hidden parent dir, so can goto destination. +;; Replace bindings for dired-hide-subdir with diredp-hide-subdir-nomove. +;; Bind dired-hide-subdir to M-$ (not $). +;; 2014/02/02 dadams +;; dired-goto-file: Redefine only for Emacs < 24. +;; 2014/01/15 dadams +;; Bind diredp-toggle-find-file-reuse-dir to C-M-R (aka C-M-S-r). +;; 2014/01/05 dadams +;; Bind dired-omit-mode (aka dired-omit-toggle) to C-x M-o. +;; 2013/12/05 dadams +;; diredp-do-grep-1: Call grep-default-command with arg, if grep+.el is loaded. +;; 2013/11/05 dadams +;; Added: diredp-get-subdirs. +;; diredp-get-files, diredp-get-files-for-dir, diredp-marked-here: Added optional arg NIL-IF-NONE-P. +;; diredp-get-files: Pass INCLUDE-DIRS-P to diredp-files-within. +;; 2013/11/04 dadams +;; Renamed Bookmarks submenus to Bookmark. +;; Added Bookmark Dired Buffer to Dir menu. +;; Alias dired-toggle-marks to dired-do-toggle for Emacs 20, instead of backwards for others. +;; Use dired-toggle-marks everywhere instead of dired-do-toggle. +;; 2013/11/03 dadams +;; Created submenus of Multiple menu: Bookmarks, Search. +;; Created submenus of Multiple > Marked Here and Below menu: +;; Images, Encryption, Search, Bookmarks. +;; Reordered menus. +;; 2013/09/26 dadams +;; diredp-next-line: Use let*, so line-move sees let bindings. +;; 2013/08/11 dadams +;; diredp-dired-files-interactive-spec: +;; Protect icicle-file-sort with boundp. Thx to Vladimir Lomov. +;; 2013/08/06 dadams +;; diredp-display-image,diredp-menu-bar-immediate-image-menu (:enable's): +;; Protect diredp-string-match-p from nil argument. +;; 2013/07/24 dadams +;; Added: diredp-nonempty-region-p. Use everywhere, in place of its definition. +;; 2013/07/21 dadams +;; Added: diredp-image-dired-(comment-file|copy-with-exif-name|(create|display)-thumb| +;; delete-tag|edit-comment-and-tags|tag-file), +;; diredp-string-match-p, diredp-menu-bar-immediate-image-menu. +;; Put this-file image commands on new menu diredp-menu-bar-immediate-image-menu. +;; diredp-menu-bar-images-menu: Added diredp-do-display-images. +;; Use diredp-string-match-p instead of string-match where appropriate. +;; diredp-find-a-file-read-args: Removed #' from lambda. +;; 2013/07/19 dadams +;; Added redefinition of dired-hide-details-mode. +;; Added: diredp-hide-details-propagate-flag, diredp-hide-details-initially-flag, +;; diredp-hide-details-last-state, diredp-hide-details-toggled, +;; diredp-hide-details-if-dired, global-dired-hide-details-mode, +;; diredp-fit-frame-unless-buffer-narrowed, diredp-hide/show-details, +;; diredp-do-display-images, diredp-display-image. +;; On dired-after-readin-hook: diredp-hide/show-details. +;; On dired-hide-details-mode-hook: diredp-fit-frame-unless-buffer-narrowed. +;; diredp-maplist: Use diredp-maplist, not maplist, in recursive call. +;; diredp-next-line: Added bobp test for negative ARG. +;; Emacs 20 line-move returns nil, so use (progn ... t). +;; Soft-require autofit-frame.el. +;; 2013/07/18 dadams +;; diredp-next-line: Protect visible-p with fboundp for Emacs 20. +;; 2013/07/17 dadams +;; Added: diredp-menu-bar-encryption-menu, diredp-menu-bar-images-menu, +;; diredp-menu-bar-immediate-encryption-menu, +;; diredp-(decrypt|verify|sign|encrypt)-this-file. +;; Added diredp-(decrypt|verify|sign|encrypt)-this-file to *-immediate-encryption-menu. +;; Moved encryption and image-dired items to the new Multiple submenus from Multiple menu. +;; 2013/07/15 dadams +;; Added: diredp-async-shell-command-this-file, diredp-do-async-shell-command-recursive. +;; Added them to menus. Bind diredp-do-async-shell-command-recursive to M-+ &. +;; diredp-menu-bar-mark-menu, diredp-dired-plus-description: Added dired-mark-omitted. +;; diredp-menu-bar-subdir-menu: Added dired-omit-mode, dired-hide-details-mode. +;; diredp-menu-bar-regexp-menu: Added image-dired-mark-tagged-files. +;; diredp-menu-bar-subdir-menu: Added dired-hide-details-mode. +;; diredp-shell-command-this-file: Corrected: provide file list to dired-do-shell-command. +;; 2013/07/13 dadams +;; diredp-font-lock-keywords-1: +;; Ensure diredp-dir-priv is not used for directory header of d:/... (Windows drive name). +;; dired-insert-directory: +;; Update wrt Emacs 24.4: Do dired-insert-set-properties last; use saved CONTENT-POINT. +;; dired-insert-set-properties: Updated for Emacs 24.4, for dired-hide-details-mode. +;; Add frame-fitting to dired-hide-details-mode-hook. +;; dired-mouse-find-file(-other-window): Error msg if click off a file name. +;; 2013/07/12 dadams +;; Added: diredp-wrap-around-flag, diredp-(next|previous)-(subdir|(dir)line). +;; Renamed dired-up-directory to diredp-up-directory. +;; Replaced vanilla commands by these new commands everywhere. +;; 2013/07/11 dadams +;; Added: diredp-up-directory-reuse-dir-buffer. +;; diredp-make-find-file-keys(-not)-reuse-dirs: Added diredp-up-directory-reuse-dir-buffer. +;; 2013/02/06 dadams +;; dired-mark-pop-up: goto point-min, so show start of file list. Thx to Michael Heerdegen. +;; 2013/01/28 dadams +;; Added redefinition of dired-do-run-mail. Fixes Emacs bug #13561. +;; 2012/12/18 dadams +;; diredp-ediff: Better default for FILE2. Thx to Michael Heerdegen. +;; Require subr-21.el for Emacs 20. +;; 2012/11/17 dadams +;; Added: derived-mode-p (for Emacs < 22), diredp-ensure-mode. +;; Use diredp-ensure-mode everywhere for mode, so compatible with Sunrise Commander etc. +;; 2012/11/01 dadams +;; Do not require ediff.el. It is required in diredp-ediff itself. +;; 2012/10/06 dadams +;; Added: minibuffer-with-setup-hook for code byte-compiled using Emacs < 22. +;; 2012/09/28 dadams +;; Moved dired-*w32* bindings after normal mouse bindings, so they override them. +;; 2012/09/05 dadams +;; diredp-(rename|copy|(rel)symlink|hardlink)-this-file: Bind use-file-dialog to nil. +;; 2012/08/26 dadams +;; Set font-lock-defaults to a 3-element list, so it works with font-menus(-da).el. +;; 2012/08/25 dadams +;; Added: redefinition of dired-pop-to-buffer (fix for bug #12281). +;; dired-mark-pop-up: If buffer is shown in a separate frame, do not show menu bar. +;; 2012/07/10 dadams +;; Removed unneeded substitute-key-definition for (next|previous)-line. +;; 2012/07/09 dadams +;; Added redefinition of dired-mark-files-regexp: Push REGEXP onto regexp-search-ring. +;; 2012/06/21 dadams +;; diredp-nb-marked-in-mode-name: +;; Add marker numbers regardless of name match. +;; Use text property dired+-mode-name to tell whether mode-name was already changed. +;; 2012/06/20 dadams +;; Added: diredp-nb-marked-in-mode-name, diredp-mode-line-(flagged|marked). Added to hooks. +;; Thx to Michael Heerdegen. +;; 2012/06/14 dadams +;; dired-mark-pop-up: Wrap save-excursion around window/frame deletion. +;; dired-do-redisplay: Updated wrt Emacs 23: bind, (then run) dired-after-readin-hook. +;; diredp-y-or-n-files-p: Corrected construction of prompt wrt final SPC. +;; 2012/06/13 dadams +;; dired-buffers-for-dir: Updated wrt Emacs 23: +;; If dired-directory is a list then expand FILE in DIR & check whether in cdr of list. +;; diredp-get-files-for-dir, diredp-files-within-1, diredp-insert-as-subdir: +;; Expand dir name before passing it to dired-buffers-for-dir. +;; 2012/06/05 dadams +;; MS Windows: Just do not define commands that are inappropriate for Windows (instead of +;; defining them to raise an error or making them invisible in menus). +;; 2012/06/02 dadams +;; Added: diredp-do-(print|encrypt|decrypt|sign|verify)-recursive. Menus. Keys. +;; diredp-do-move-recursive: Corrected to use dired-rename-file, not dired-copy-file. +;; 2012/05/30 dadams +;; diredp-insert-as-subdir: Added optional arg IN-DIRED-NOW-P. Pick up markings & switches +;; from sole Dired buffer for CHILD if not in Dired now. +;; 2012/05/29 dadams +;; Added: diredp-do-(chxxx|chgrp|chown|touch)-recursive, diredp-touch-this-file, +;; diredp-menu-bar-(immediate|operate)-bookmarks-menu. Added to menus. Bound to keys. +;; Factored bookmark stuff into Bookmark(s) submenus. +;; diredp-menu-bar-immediate-menu: Added dired-kill-subdir, [goto-subdir]. +;; diredp-dired-this-subdir, dired-maybe-insert-subdir: Corrected :visible/enable. +;; diredp-dired-inserted-subdirs: Do dired-(remember-marks|mark-remembered) in this-buff. +;; diredp-mouse-3-menu: +;; Do not use save-excursion, because some commands move point on purpose. Just return to +;; original point unless command intends to MOVEP. +;; Added to menu dired-maybe-insert-subdir (two entries), dired-kill-subdir. +;; Use *-this-file*, not *-do-*: copy|symlink|shell-command|grep|load (don't use :keys). +;; 2012/05/26 dadams +;; diredp-dired-inserted-subdirs, diredp-insert-as-subdir: +;; Preserve markings and switches in target buffer. +;; dired-mark-pop-up: Use unwind-protect. Bury buffer too. +;; diredp-do-chmod-recursive: Use only 5 args if < Emacs 23. +;; 2012/05/25 dadams +;; Added: diredp-insert-as-subdir, diredp-ancestor-dirs, diredp-maplist, +;; diredp-do-redisplay-recursive, diredp-do-chmod-recursive. +;; Bound diredp-do-chmod-recursive. to M-+ M and added to menu. +;; diredp-get-files: Added optional arg DONT-ASKP. +;; diredp-y-or-n-files-p: Kill list buffer if it was never shown. +;; dired-mark-pop-up: ignore error when delete frame/window. +;; 2012/05/22 dadams +;; diredp-get-files(-for-dir): Added optional arg INCLUDE-DIRS-P. +;; Added: diredp-insert-subdirs(-recursive), diredp(-this)-dired-inserted-subdir(s). +;; Added to menus. Bound diredp-insert-subdirs* to (M-+) M-i. +;; Bound diredp-capitalize(-recursive) to (M-+) %c. +;; Added diredp-dired-union-other-window to Dirs menu. +;; Updated diredp-dired-plus-description. +;; 2012/05/19 dadams +;; Added: diredp-image-dired-*-recursive, diredp-*link-recursive, +;; diredp-do-isearch(-regexp)-recursive, diredp-do-query-replace-regexp-recursive, +;; diredp-do-search-recursive, diredp-(capitalize|(up|down)case)-recursive, +;; diredp-create-files-non-directory-recursive. +;; Bound on M-+ prefix key. Added to menus. +;; diredp-get-files, diredp-y-or-n-files-p, diredp-list-files, diredp-list-marked-recursive: +;; Added optional arg PREDICATE. +;; diredp-do-create-files-recursive: Removed MARKER-CHAR arg. Hard-code to keep markings. +;; diredp-do-(copy|move)-recursive: Use arg IGNORE-MARKS-P (forgot to use it). +;; Removed MARKER-CHAR arg in call to d-d-c-f-r. +;; Added missing autoload cookies. +;; 2012/05/06 dadsms +;; diredp-y-or-n-files-p: Do not kill buffer *Files* - just bury it. +;; 2012/05/05 dadams +;; Added: diredp-do-bookmark-recursive, diredp-do-bookmark-in-bookmark-file-recursive, +;; diredp-set-bookmark-file-bookmark-for-marked-recursive. +;; Bound to M-+ M-b, M-+ C-M-B (aka C-M-S-b), M-+ C-M-b, respectively. Added to menus. +;; diredp-bookmark: Added optional arg FILE. +;; diredp-do-bookmark-in-bookmark-file: Added optional arg FILES. +;; diredp-dired-plus-description: Updated. +;; diredp-get-confirmation-recursive: Raise error if not in Dired. +;; diredp-do-bookmark-recursive, diredp-marked-recursive(-other-window), +;; diredp-multiple-w32-browser-recursive: +;; Use diredp-get-confirmation-recursive. +;; 2012/05/04 dadams +;; Added: dired-mark-unmarked-files for Emacs < 24. +;; diredp-do-create-files-recursive: Corrected for Emacs < 24. +;; diredp-do-create-files-recursive, diredp-(un)mark-files-tagged-regexp, +;; diredp(-mouse)-do-(un)tag, diredp(-mouse)-do-remove-all-tags, +;; diredp(-mouse)-do-paste-(add|replace)-tags, diredp(-mouse)-do-set-tag-value, +;; diredp(-mouse)-do-bookmark(-in-bookmark-file), diredp-find-a-file-read-args, +;; diredp-mouse-do-shell-command: +;; Use lexical-let(*), to get closures for free vars in lambdas. +;; 2012/04/28 dadams +;; Added: +;; diredp-copy-filename-as-kill-recursive, diredp-do-copy-recursive, +;; diredp-do-find-marked-files-recursive, diredp-do-grep-recursive, +;; diredp-do-move-recursive, diredp-do-shell-command-recursive, +;; diredp-list-marked-recursive, diredp-marked-recursive(-other-window), +;; diredp-multiple-w32-browser-recursive, diredp-do-create-files-recursive, +;; diredp-get-confirmation-recursive, diredp-list-files, diredp-y-or-n-files-p, +;; diredp-menu-bar-recursive-marked-menu. +;; diredp-get-files: Use diredp-y-or-n-files-p, not y-or-n-p. +;; Commented out dired-readin-insert - see comment. +;; Moved bookmark menu items to submenu Bookmarks. +;; Added keys (with M-+ prefix) and menu items for new (*-recursive) commands. +;; Reordered w32-browser stuff in menus. +;; diredp-do-grep: Combined defs for diff Emacs versions - do version test at runtime. +;; 2012/04/25 dadams +;; dired-insert-directory: Updated per Emacs 24. +;; 2012/04/23 dadams +;; Added (moved here from Icicles, and renamed prefix): +;; diredp-re-no-dot, diredp-get-files, diredp-get-files-for-dir, diredp-files-within, +;; diredp-files-within-dirs-done. +;; 2012/04/05 dadams +;; Added redefinition of dired-mark-pop-up, to fix Emacs bug #7533. If they ever fix it +;; then remove this hack. +;; 2012/03/13 dadams +;; diredp-dired(-for)-files(-other-window): +;; Bind: icicle-sort-comparer, icicle-all-candidates-list-alt-action-fn. +;; Use icicle-(un)bind-file-candidate-keys. +;; diredp-dired-files-interactive-spec: Updated doc strings accordingly. +;; 2012/03/07 dadams +;; Added: dired-switches-escape-p. +;; dired-get-filename: Updated wrt Emacs 24: +;; whitespace quoting for bug #10469, filename quoting per Emacs 23.3+, +;; MS Windows conversion of \ to / per Emacs 23.3+. +;; dired-goto-file: Escape whitespace, per Emacs 24 (for bug #10469). +;; 2012/03/02 dadams +;; Require cl.el at compile time even for Emacs 22+, for case macro. +;; 2012/02/28 dadams +;; Do not bother to soft-require mkhtml.el anymore. +;; 2012/02/18 dadams +;; Swapped keys for dired-w32(-browser|explore), so the former is M-RET, as in Bookmark+. +;; 2012/01/10 dadams +;; diredp-font-lock-keywords-1: Corrected for date/time when locale is used, not iso. +;; 2011/12/19 dadams +;; dired-insert-set-properties, dired-mark-sexp, diredp-(un)mark-region-files, +;; diredp-flag-region-files-for-deletion, diredp-mouse-3-menu: +;; Use line-(beginning|end)-position. +;; 2011/12/16 dadams +;; diredp-menu-bar-mark-menu: Removed Revert item. +;; diredp-menu-bar-subdir-menu: Add image-dired-dired-toggle-marked-thumbs. +;; diredp-mouse-3-menu: +;; Use commands bound to keys, so the keys show up in the menu. Prefer *-this-file. +;; Correct the mark/unmark/flag menu-item visibility. Added Capitalize. +;; 2011/12/09 dadams +;; diredp-w32-drives: Use dolist, not mapcar. +;; diredp-mouse-3-menu: Use easymenu to build the menu. Conditionalize some items. +;; Bind down-mouse-3, not mouse-3, to diredp-mouse-3-menu. (bind mouse-3 to ignore). +;; Added eval-when-compile for easymenu.el. +;; 2011/12//02 dadams +;; Added diredp-internal-do-deletions. +;; dired(-mouse)-do(-flagged)-delete, : Use diredp-internal-do-deletions, for trash. +;; 2011/11/29 dadams +;; diredp-read-bookmark-file-args: Corrected use of list of default file names: > Emacs 23.1. +;; 2011/10/31 dadams +;; dired-mode-hook: Call font-lock-refresh-defaults - see Emacs 24 bugs #6662 and #9919. +;; 2011/10/24 dadams +;; Protect dired-show-file-type with fboundp. +;; 2011/09/03 dadams +;; diredp-do-grep-1: Map shell-quote-argument over file names. Thx to Joe Bloggs. +;; 2011/08/07 dadams +;; diredp-bookmark (need to keep in sync with bmkp-make-record-for-target-file): +;; Instead of image-bookmark-make-record, use explicit function that includes file, type. +;; 2011/07/25 dadams +;; Changed featurep to eval-after-load, for bookmark+-1.el and w32-browser.el. +;; 2011/07/01 dadams +;; Fixed typo: dired-toggle-find-file-reuse-dir -> ...diredp.... Thx to pasja on Emacs Wiki. +;; 2011/06/18 dadams +;; Added: diredp-describe-mode, diredp-dired-plus-help(-link), diredp-help-button, +;; diredp-dired-plus-description(+links), diredp-send-bug-report. +;; Bound diredp-describe-mode to whatever describe-mode is bound to. +;; All menus, :enable with mark-active: Added transient-mark-mode and mark != point. +;; toggle-diredp-find-file-reuse-dir: Swapped which one is the alias. +;; diredp-w32-list-mapped-drives: Display *Shell Command Output* at end. +;; diredp-mouse-(describe-file|3-menu|mark/unmark|(find|view)-file(-other-window)): +;; save-excursion set-buffer -> with-current-buffer. +;; 2011/06/08 dadams +;; Added: diredp-dired-for-files(-other-window). +;; 2011/06/07 dadams +;; Bound dired-show-file-type to _, since y is diredp-relsymlink-this-file. +;; 2011/04/25 dadams +;; Added (from files+.el): dired(-mouse)-describe-file. Bound to C-h (C-)RET, added to menus. +;; 2011/04/23 dadams +;; Added, bound (T c, T M-w, T 0, T v, T p, T C-y, T q), and added to menus: +;; diredp-copy-tags-this-file, diredp-mouse-copy-tags, +;; diredp(-mouse)(-do)-remove-all-tags(-this-file), +;; diredp(-mouse)(-do)-set-tag-value(-this-file), +;; diredp(-mouse)(-do)-paste-(add|replace)-tags(-this-file). +;; diredp-mark-files-tagged-(all/none|some/not-all): Bound free var presentp. +;; dired-map-over-marks: Corrected: Bind NEWARG and use that, not ARG. +;; dired-get-marked-files: let* -> let. +;; dired-do-redisplay, diredp-mouse-diff: when/if -> and. +;; dired-readin-insert, dired-get-filename: if -> unless/when. +;; diredp-mouse-find-file-reuse-dir-buffer: with-current-buffer, not save... +;; diredp-mouse-mark/unmark: Removed unused bol/eol vars. +;; 2011/04/19 dadams +;; Added: diredp-(un)mark-files-tagged-((not-)all|none|some|regexp|all/none|some/not-all), +;; dired-mark-if. Added Tagged submenu for Mark menu. +;; Put tags commands on prefix key T, as in Bookmark+. Removed C-(M-)+/- tags-cmd bindings. +;; diredp-untag-this-file: Added prefix-arg behavior. +;; 2011/04/18 dadams +;; Added: diredp-prompt-for-bookmark-prefix-flag. +;; Use it in diredp(-mouse)-do-(un)tag, diredp-read-bookmark-file-args, +;; diredp(-mouse)-do-bookmark, diredp-(bookmark|(un)tag)-this-file. +;; diredp-(bookmark|(un)tag)-this-file, diredp(-do)-bookmark, diredp-(un)tag, +;; diredp-do-bookmark-in-bookmark-file, diredp-set-bookmark-file-bookmark-for-marked: +;; Made PREFIX arg optional. +;; 2011/04/17 dadams +;; Added: diredp-(bookmark|(un)tag)-this-file, diredp(-mouse)(-do)-(un)tag. +;; diredp-mouse-3-menu: Added: diredp-mouse-do-(un)tag. +;; diredp-menu-bar-immediate-menu: Added diredp-(un)tag-this-file, diredp-bookmark-this-file. +;; diredp-menu-bar-operate-menu: Added diredp-do-(un)tag. +;; Bound diredp-do-tag to C-+, diredp-tag-this-file to C-M-+, diredp-do-untag to C--, +;; diredp-untag-this-file to C-M--, diredp-bookmark-this-file to C-B. +;; diredp-bookmark: Use bmkp-autofile-set, not bmkp-file-target-set, so get autofile. +;; diredp-read-bookmark-file-args, diredp(-mouse)-do-bookmark: +;; Default for prefix is now an empty string, not the directory. +;; diredp-mouse-do-bookmark: Removed optional second arg. +;; Corrected typo: direp-read-bookmark-file-args -> diredp-read-bookmark-file-args. +;; 2011/03/25 dadams +;; diredp-bookmark: Fixed typo: bmkp-file-indirect-set -> bmkp-file-target-set. +;; 2011/02/11 dadams +;; diredp-deletion, diredp-deletion-file-name, diredp-executable-tag: +;; Made default the same for dark background as for light. +;; diredp-ignored-file-name: Made default a bit darker for dark background. +;; 2011/02/03 dadams +;; All deffaces: Provided default values for dark-background screens too. +;; 2011/01/12 dadams +;; dired-do-flagged-delete: Removed sit-for added on 1/02. +;; 2011/01/04 dadams +;; defsubst -> defun everywhere. +;; Removed autoload cookies from non def* sexps, defvar, and non-interactive functions. +;; Added some missing autoload cookies for defcustom and commands. +;; 2011/01/02 dadams +;; Added: diredp-this-file-(un)marked-p, diredp-toggle-marks-in-region. +;; diredp-(un)mark-region-files, diredp-flag-region-files-for-deletion: +;; Act only on marked/unmarked files (opposite). Fix 2nd arg to dired-mark-if. +;; diredp-mouse-3-menu: +;; If region is active and mouse3.el was loaded, then use its popup. +;; Fix Toggle Marked/Unmarked: +;; Use diredp-toggle-marks-in-region, so widen, show details and use bol/eol. +;; dired-do-flagged-delete: Added sit-for. +;; 2010/11/28 dadams +;; diredp-mouse-3-menu: Added Toggle Marked/Unmarked for region menu. +;; 2010/10/20 dadams +;; Moved Emacs 20 tweak to recognize k in file sizes to var dired-move-to-filename-regexp. +;; Added diredp-loaded-p. +;; 2010/10/19 dadams +;; diredp-font-lock-keywords-1: +;; Handle decimal pt in file size. Thx to Michael Heerdegen. +;; Enable Emacs 20/21 to handle -h option (decimal point size). +;; Renamed: face diredp-inode+size to diredp-number. +;; 2010/10/01 dadams +;; dired-goto-file: Avoid infloop from looking for dir line. Thx to not-use.dilines.net. +;; 2010/09/29 dadams +;; Added: diredp-dired-union(-1|-other-window|-interactive-spec). +;; dired-goto-file: fix for Emacs bug #7126. +;; 2010/09/27 dadams +;; Renamed diredp-dired-interactive-spec to diredp-dired-files-interactive-spec. +;; diredp-dired-files-interactive-spec: Respect file-list arg: kill existing Dired buffer. +;; Fix use of prefix arg for switches. +;; 2010/09/26 dadams +;; Added: dired-insert-directory: Compute WILDCARD arg for individual files. +;; Added: dired-readin-insert: Use t as WILDCARD arg to dired-insert-directory. +;; Added: diredp-dired-files(-other-window), diredp-dired-interactive-spec. +;; 2010/08/27 dadams +;; Use diredp-font-lock-keywords-1 properly as a second level of fontification. +;; Added: diredp-w32-drives(-mode(-map)), dired-up-directory. +;; 2010/08/07 dadams +;; dired-map-over-marks: Removed loop that used dired-between-files. +;; diredp-get-file-or-dir-name: test against subdir/..? also. +;; dired-do-find-marked-files: Pass original ARG to dired-get-marked-files. +;; 2010/08/05 dadams +;; diredp-bookmark: +;; Handle image files (and sound files, if Bookmark+ is used). +;; Use bmkp-file-indirect-set if available. +;; Use error-message-string to get failure msg. +;; 2010/07/11 dadams +;; Added: diredp-set-bookmark-file-bookmark-for-marked (C-M-b), diredp-mouse-do-bookmark, +;; diredp-do-bookmark-in-bookmark-file (C-M-B, aka C-M-S-b), diredp-read-bookmark-file-args. +;; Added them to the operate menu. Added diredp-do-bookmark to mouse-3 menu. +;; 2010/07/07 dadams +;; dired-do-*: Updated doc strings for prefix arg treatment from dired-map-over-marks-check. +;; Added missing autoload cookies. +;; 2010/05/29 dadams +;; diredp-bookmark: Use relative file name in bookmark name. +;; Removed defvar of directory-listing-before-filename-regexp. +;; 2010/05/28 dadams +;; Changed menu item for dired-create-directory to New Directory. Moved it before Up Dir. +;; 2010/03/19 dadams +;; diredp-font-lock-keywords-1: Handle date+time wrt regexp changes for Emacs 23.2. +;; 2010/01/31 dadams +;; diredp-bookmark: +;; Don't use bookmark-set or find-file-noselect - inline the needed bookmark-store code. +;; Call bookmark-maybe-load-default-file. Use rudimentary bookmark-make-record-function. +;; 2010/01/21 dadams +;; Renamed: +;; diredp-subst-find-alternate-for-find to diredp-make-find-file-keys-reuse-dirs +;; diredp-subst-find-for-find-alternate to diredp-make-find-file-keys-not-reuse-dirs. +;; diredp-make-find-file-keys(-not)-reuse-dirs: Handle also dired(-mouse)-w32-browser. +;; 2010/01/10 dadams +;; Added: face diredp-inode+size. Use in diredp-font-lock-keywords-1. +;; diredp-font-lock-keywords-1: Allow decimal point in file size. Thx to Regis. +;; 2010/01/05 dadams +;; dired-insert-set-properties: +;; Add text property dired-filename to the file name (for Emacs 23). +;; 2009/10/23 dadams +;; diredp-font-lock-keywords-1: Override `l' and `t' matches in headings with default face. +;; 2009/10/13 dadams +;; Added: diredp(-do)-bookmark. Added to Multiple menu, and bound to M-b. +;; 2009/10/11 dadams +;; diredp-menu-bar-immediate-menu: +;; Added items: image display items, dired-maybe-insert-subdir. +;; Test dired-do-relsymlink, not diredp-relsymlink-this-file. +;; diredp-menu-bar-operate-menu: +;; Added items: epa encryption items, image items, isearch items. +;; diredp-menu-bar-subdir-menu: +;; Added items: revert, isearch file names, dired-compare-directories. +;; Removed macro menu-item-any-version - use menu-item everywhere (works for Emacs 20+). +;; Added wdired-change-to-wdired-mode to subdir menu even for Emacs 20, if defined. +;; 2009/07/09 dadams +;; dired-goto-file: Make sure we have a string before calling directory-file-name. +;; 2009/05/08 dadams +;; dired-find-file (Emacs 20): Raise error if dired-get-filename returns nil. +;; 2009/04/26 dadams +;; dired-insert-set-properties, diredp-(un)mark-region-files, +;; diredp-flag-region-files-for-deletion, diredp-mouse-3-menu, diredp-mouse-mark/unmark: +;; Bind inhibit-field-text-motion to t, to ensure real eol. +;; 2008/12/17 dadams +;; diredp-font-lock-keywords-1: Don't do diredp-deletion, diredp-flag-mark for empty lines. +;; 2008/09/22 dadams +;; Added: diredp-fileset, diredp-get-file-or-dir-name, and redefinitions of +;; dired-map-over-marks, dired-find-file, and dired-mouse-find-file-other-window. +;; Added vanilla code to pick up macro dired-map-over-marks: +;; dired-get-marked-files, dired-do-delete, dired-map-over-marks-check, +;; dired-do-redisplay, image-dired-dired-insert-marked-thumbs. +;; diredp-find-file-other-frame, diredp-mouse-(find|view)-file: +;; Added nil t args to dired-get-filename calls. +;; diredp-do-grep(-1): Use new dired-get-marked-files instead of ad-hoc treatment of C-u. +;; 2008/09/21 dadams +;; diredp-marked(-other-window): Don't treat zero prefix arg as numerical (no empty Dired). +;; Added dired-find-file redefinition for Emacs 20. +;; 2008/09/11 dadams +;; diredp-do-grep: Plain C-u means grep all files in Dired buffer. +;; diredp-do-grep-1: Treat 'all value of FILES arg. +;; Added: diredp-all-files. +;; 2008/09/09 dadams +;; Added: diredp-marked(-other-window). Added to menus. Bound *-other-window to C-M-*. +;; 2008/09/07 dadams +;; Added: diredp(-mouse)-do-grep(-1), diredp-grep-this-file. +;; Bound diredp-do-grep to M-g. Added grep commands to menus. +;; 2008/07/18 dadams +;; Soft-require w32-browser.el. Bind its commands in Dired map and menus. +;; 2008/03/08 dadams +;; dired-maybe-insert-subdir: Fit one-window frame after inserting subdir. +;; 2008/03/07 dadams +;; Added: redefinitions of dired-maybe-insert-subdir, dired-goto-file, dired-get-filename. +;; Added: diredp-this-subdir. +;; 2007/11/27 dadams +;; diredp-mouse(-backup)-diff: If available, use icicle-read-string-completing. +;; 2007/09/23 dadams +;; Removed second arg to undefine-killer-commands. +;; 2007/07/27 dadams +;; diredp-font-lock-keywords-1: Allow also for bz2 compressed files - Thx to Andreas Eder. +;; 2006/09/03 dadams +;; diredp-font-lock-keywords-1: Corrected file size and inode number. Thx to Peter Barabas. +;; 2006/08/20 dadams +;; Added: diredp-find-a-file*. +;; 2006/06/18 dadams +;; diredp-font-lock-keywords-1: Highlight file name (also) of flagged files. +;; Use dired-del-marker instead of literal D. +;; Added: diredp-deletion-file-name. +;; 2006/03/31 dadams +;; No longer use display-in-minibuffer. +;; 2006/01/07 dadams +;; Added: link for sending bug report. +;; 2006/01/06 dadams +;; Added defgroup Dired-Plus and used it. Added :link. +;; 2006/01/04 dadams +;; Added defvar of directory-listing-before-filename-regexp, for Emacs 22 compatibility. +;; 2005/12/29 dadams +;; Added: diredp-mouse-mark/unmark-mark-region-files. +;; 2005/12/26 dadams +;; Updated groups. +;; 2005/12/05 dadams +;; diredp-ignored-file-name: Made it slightly darker. +;; 2005/11/05 dadams +;; Renamed all stuff defined here to have diredp- prefix. +;; diredp-relsymlink-this-file: Protected with fboundp. +;; Changed to soft require: dired-x.el. +;; Removed comment to require this inside eval-after-load. +;; 2005/11/03 dadams +;; Added: dired-display-msg. Replace blue-foreground-face with it. +;; Alias dired-do-toggle to dired-toggle-marks, if defined. +;; 2005/11/02 dadams +;; Added: dired-get-file-for-visit, dired(-mouse)-find-alternate-file*, +;; togglep-dired-find-file-reuse-dir, dired+-subst-find-*. +;; Use defface for all faces. Renamed without "-face". No longer require def-face-const. +;; dired-simultaneous-find-file: Minor bug fix (typo). +;; 2005/07/10 dadams +;; dired-unmark-all-files-no-query -> dired-unmark-all-marks +;; (thanks to Sivaram Neelakantan for bug report). +;; 2005/05/25 dadams +;; string-to-int -> string-to-number everywhere. +;; 2005/05/17 dadams +;; Updated to work with Emacs 22.x. +;; 2005/02/16 dadams +;; Added dired-mark/unmark-extension. Replaced dired-mark-extension with it everywhere. +;; 2005/01/08 dadams +;; Bind [S-mouse-1], instead of [S-down-mouse-1], to dired-mouse-mark-region-files. +;; 2004/11/20 dadams +;; dired-mark-sexp: Search for literal month names only for versions before Emacs 20. +;; Refined to deal with Emacs 21 < 21.3.50 (soon to be 22.x) +;; 2004/11/14 dadams +;; Bound dired-no-confirm to non-nil for dired-mouse-*. +;; Updated for Emacs 21 and improved highlighting: +;; Spaces OK in file and directory names. Highlight date/time and size. +;; 2004/10/17 dadams +;; Require cl only for Emacs 20, and only when compile. +;; 2004/10/01 dadams +;; Updated to work with Emacs 21 also. +;; 2004/04/02 dadams +;; dired-font-lock-keywords-1: Prefer using dired-omit-extensions +;; to completion-ignored-extensions, if available. +;; 2004/03/22 dadams +;; Added dired-mouse-mark-region-files and dired-mouse-mark/unmark. +;; 2000/09/27 dadams +;; 1. dired-font-lock-keywords-1: fixed for spaces in dir names. +;; 2. Added: dired-buffers-for-dir. +;; 1999/09/06 dadams +;; Added S-*-mouse-2 bindings (same as C-*-mouse-2). +;; 1999/08/26 dadams +;; 1. Added *-face vars and dired-font-lock-keywords-1. +;; 2. Added possibility to use dired-font-lock-keywords-1 via hook. +;; 1999/08/26 dadams +;; Changed key binding of dired-mouse-find-file from down-mouse-2 to mouse-2. +;; 1999/08/25 dadams +;; Changed (C-)(M-)mouse-2 bindings. +;; 1999/08/25 dadams +;; 1. Added cmds & menu bar and key bindings: (dired-)find-file-other-frame. +;; 2. Changed binding for dired-display-file. +;; 1999/03/26 dadams +;; 1. Get rid of Edit menu-bar menu. +;; 2. dired-mouse-3-menu: Changed popup titles and item names. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth +;; Floor, Boston, MA 02110-1301, USA. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Code: + +(eval-when-compile (require 'cl)) ;; case (plus, for Emacs 20: dolist, pop, push) +(eval-when-compile (require 'easymenu)) ;; easy-menu-create-menu + +(require 'dired) ;; dired-revert +(require 'dired-aux) ;; dired-bunch-files, dired-do-chxxx, dired-do-create-files, + ;; dired-mark-read-string, dired-read-shell-command, + ;; dired-run-shell-command, dired-shell-stuff-it +(require 'dired-x) ;; dired-do-relsymlink +(require 'autofit-frame nil t) ;; (no error if not found) fit-frame-if-one-window +(require 'bookmark+ nil t) ;; (no error if not found) + ;; bmkp-autofile-add-tags, bmkp-autofile-remove-tags, bmkp-autofile-set, bmkp-copied-tags, + ;; bmkp-current-bookmark-file, bmkp-describe-bookmark, bmkp-empty-file, bmkp-get-autofile-bookmark, + ;; bmkp-get-bookmark-in-alist, bmkp-get-tags, bmkp-read-tag-completing, + ;; bmkp-read-tags-completing, bmkp-refresh/rebuild-menu-list, bmkp-remove-all-tags, + ;; bmkp-same-file-p, bmkp-set-bookmark-file-bookmark, bmkp-set-sequence-bookmark, + ;; bmkp-set-tag-value, bmkp-some, bmkp-switch-bookmark-file, bmkp-tag-name + +;; For now at least, `highlight.el' is needed only if you use `bookmark+.el'. +(when (featurep 'bookmark+) (require 'highlight nil t)) ;; (no error if not found): + ;; hlt-highlight-region + +(if (> emacs-major-version 21) (require 'help-fns+ nil t) (require 'help+20 nil t)) ;; (no error if not found): + ;; describe-file + +(require 'misc-fns nil t) ;; (no error if not found): undefine-killer-commands +(require 'image-file nil t) ;; (no error if not found): image-file-name-regexp +(require 'image-dired nil t) ;; (no error if not found): + ;; image-dired-create-thumb, image-dired-create-thumbnail-buffer, + ;; image-dired-dired-after-readin-hook, image-dired-delete-tag, image-dired-dired-comment-files, + ;; image-dired-dired-display-external, image-dired-dired-display-image, + ;; image-dired-display-thumbs, image-dired-get-comment, image-dired-get-exif-file-name, + ;; image-dired-get-thumbnail-image, image-dired-insert-thumbnail, image-dired-line-up, + ;; image-dired-line-up-dynamic, image-dired-line-up-interactive, image-dired-line-up-method, + ;; image-dired-list-tags, image-dired-main-image-directory, image-dired-mark-tagged-files, + ;; image-dired-read-comment, image-dired-remove-tag, image-dired-save-information-from-widgets, + ;; image-dired-tag-files, image-dired-thumb-height, image-dired-thumbnail-buffer, + ;; image-dired-thumb-name, image-dired-thumb-size, image-dired-thumb-width, + ;; image-dired-widget-list, image-dired-write-comments, image-dired-write-tags +(when (memq system-type '(windows-nt ms-dos)) + ;; (no error if not found): + (require 'w32-browser nil t));; dired-w32explore, dired-w32-browser, dired-mouse-w32-browser, + ;; dired-multiple-w32-browser +(when (< emacs-major-version 21) (require 'subr-21)) ;; replace-regexp-in-string + +;; Provide macro for code byte-compiled using Emacs < 22. +(eval-when-compile + (when (< emacs-major-version 22) + (defmacro minibuffer-with-setup-hook (fun &rest body) + "Temporarily add FUN to `minibuffer-setup-hook' while executing BODY. +BODY should use the minibuffer at most once. +Recursive uses of the minibuffer are unaffected (FUN is not +called additional times). + +This macro actually adds an auxiliary function that calls FUN, +rather than FUN itself, to `minibuffer-setup-hook'." + ;; (declare (indent 1) (debug t)) + (let ((hook (make-symbol "setup-hook"))) + `(let (,hook) + (setq ,hook (lambda () + ;; Clear out this hook so it does not interfere + ;; with any recursive minibuffer usage. + (remove-hook 'minibuffer-setup-hook ,hook) + (funcall ,fun))) + (unwind-protect + (progn (add-hook 'minibuffer-setup-hook ,hook) ,@body) + (remove-hook 'minibuffer-setup-hook ,hook))))))) + +(defmacro diredp-user-error (&rest args) + `(if (fboundp 'user-error) (user-error ,@args) (error ,@args))) + +;; Define these for Emacs 20 and 21. +(unless (fboundp 'dired-get-file-for-visit) ; Emacs 22+ + (defun dired-get-file-for-visit () ; Not bound + "Get the current line's file name, with an error if file does not exist." + (interactive) + (let ((raw (dired-get-filename nil t)) ; Pass t for second arg so no error for `.' and `..'. + file-name) + (unless raw (error "No file on this line")) + (setq file-name (file-name-sans-versions raw t)) + (if (file-exists-p file-name) + file-name + (if (file-symlink-p file-name) + (error "File is a symlink to a nonexistent target") + (error "File no longer exists; type `g' to update Dired buffer"))))) + + (defun dired-find-alternate-file () ; Not bound + "In Dired, visit this file or directory instead of the Dired buffer." + (interactive) + (set-buffer-modified-p nil) + (find-alternate-file (dired-get-file-for-visit)))) + +;;;;;;;;;;;;;;;;;;;;;;; + + +(provide 'dired+) +(require 'dired+) ; Ensure loaded before compile this. + +;; Quiet the byte-compiler. +(defvar bmkp-copied-tags) ; In `bookmark+-1.el' +(defvar bmkp-current-bookmark-file) ; In `bookmark+-1.el' +(defvar bookmark-default-file) ; In `bookmark.el' +(defvar compilation-current-error) ; In `compile.el' +(defvar delete-by-moving-to-trash) ; Built-in, Emacs 23+ +(defvar dired-always-read-filesystem) ; In `dired.el', Emacs 26+ +(defvar dired-auto-revert-buffer) ; In `dired.el', Emacs 23+ +(defvar dired-create-files-failures) ; In `dired-aux.el', Emacs 22+ +(defvar dired-details-state) ; In `dired-details+.el' +(defvar dired-keep-marker-hardlink) ; In `dired-x.el' +(defvar dired-overwrite-confirmed) ; In `dired-aux.el' +(defvar dired-query-alist) ; In `dired-aux.el', Emacs < 24 +(defvar dired-recursive-copies) ; In `dired-aux.el', Emacs 22+ +(defvar dired-recursive-deletes) ; In `dired.el', Emacs 22+ +(defvar dired-shrink-to-fit) ; In `dired.el' +(defvar dired-switches-alist) ; In `dired.el' +(defvar dired-subdir-switches) ; In `dired.el' +(defvar dired-touch-program) ; Emacs 22+ +(defvar dired-use-ls-dired) ; Emacs 22+ +(defvar diredp-count-.-and-..-flag) ; Here, Emacs 22+ +(defvar diredp-hide-details-initially-flag) ; Here, Emacs 24.4+ +(defvar diredp-hide-details-last-state) ; Here, Emacs 24.4+ +(defvar diredp-hide-details-propagate-flag) ; Here, Emacs 24.4+ +(defvar diredp-hide-details-toggled) ; Here, Emacs 24.4+ +(defvar diredp-highlight-autofiles-mode) ; Here, Emacs 22+ +(defvar diredp-menu-bar-encryption-menu) ; Here, Emacs 23+ +(defvar diredp-menu-bar-images-recursive-menu) ; Here (old name) +(defvar diredp-menu-bar-regexp-recursive-menu) ; Here (old name) +(defvar diredp-menu-bar-subdir-menu) ; Here (old name) +(defvar diredp-move-file-dirs) ; Here, Emacs 24+ +(defvar diredp-single-bookmarks-menu) ; Here, if Bookmark+ is available +(defvar filesets-data) ; In `filesets.el' +(defvar grep-use-null-device) ; In `grep.el' +(defvar header-line-format) ; Emacs 22+ +(defvar icicle-file-sort) ; In `icicles-opt.el' +;; $$$$ (defvar icicle-file-sort-first-time-p) ; In `icicles-var.el' +(defvar icicle-files-ido-like-flag) ; In `icicles-opt.el' +(defvar icicle-ignored-directories) ; In `icicles-opt.el' +(defvar icicle-sort-comparer) ; In `icicles-opt.el' +(defvar image-dired-display-image-buffer) ; In `image-dired.el' +(defvar image-dired-line-up-method) ; In `image-dired.el' +(defvar image-dired-main-image-directory) ; In `image-dired.el' +(defvar image-dired-thumbnail-buffer) ; In `image-dired.el' +(defvar image-dired-thumb-height) ; In `image-dired.el' +(defvar image-dired-thumb-width) ; In `image-dired.el' +(defvar image-dired-widget-list) ; In `image-dired.el' +(defvar ls-lisp-use-insert-directory-program) ; In `ls-lisp.el' +(defvar minibuffer-default-add-function) ; In `simple.el', Emacs 23+ +(defvar mouse3-dired-function) ; In `mouse3.el' +(defvar read-file-name-completion-ignore-case) ; In `minibuffer.el', Emacs 23+. In C code, Emacs 22. +(defvar recentf-list) ; In `recentf.el' +(defvar switch-to-buffer-preserve-window-point) ; In `window.el', Emacs 24+ +(defvar tooltip-mode) ; In `tooltip.el' +(defvar vc-directory-exclusion-list) ; In `vc' +(defvar w32-browser-wait-time) ; In `w32-browser.el' + +;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup Dired-Plus nil + "Various enhancements to Dired." + :prefix "diredp-" :group 'dired + :link `(url-link :tag "Send Bug Report" + ,(concat "mailto:" "drew.adams" "@" "oracle" ".com?subject=\ +dired+.el bug: \ +&body=Describe bug here, starting with `emacs -q'. \ +Don't forget to mention your Emacs and library versions.")) + :link '(url-link :tag "Other Libraries by Drew" + "https://www.emacswiki.org/emacs/DrewsElispLibraries") + :link '(url-link :tag "Download" + "https://www.emacswiki.org/emacs/download/dired%2b.el") + :link '(url-link :tag "Description" + "https://www.emacswiki.org/emacs/DiredPlus") + :link '(emacs-commentary-link :tag "Commentary" "dired+")) + +;;; Variables + +;; `dired-do-toggle' was renamed to `dired-toggle-marks' after Emacs 20. +(unless (fboundp 'dired-toggle-marks) (defalias 'dired-toggle-marks 'dired-do-toggle)) + +;;; This is duplicated in `diff.el' and `vc.el'. +;;;###autoload +(defcustom diff-switches "-c" + "*A string or list of strings specifying switches to be passed to diff." + :type '(choice string (repeat string)) + :group 'dired :group 'diff) + +;;;###autoload +(defcustom diredp-auto-focus-frame-for-thumbnail-tooltip-flag nil + "*Non-nil means automatically focus the frame for a thumbnail tooltip. +If nil then you will not see a thumbnail image tooltip when you +mouseover an image-file name in Dired, unless you first give the frame +the input focus (e.g., by clicking its title bar). + +This option has no effect if `diredp-image-preview-in-tooltip' is nil. +It also has no effect for Emacs versions prior to Emacs 22." + :type 'boolean :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-bind-problematic-terminal-keys t + "*Non-nil means bind some keys that might not work in a text-only terminal. +This applies to keys that use modifiers Meta and Shift together. +If you use Emacs in text-only terminal and your terminal does not +support the use of such keys then customize this option to nil." + :type 'boolean :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-compressed-extensions '(".tar" ".taz" ".tgz" ".arj" ".lzh" + ".lzma" ".xz" ".zip" ".z" ".Z" ".gz" ".bz2" ".rar" ".rev") + "*List of compressed-file extensions, for highlighting. + +Note: If you change the value of this option then you need to restart +Emacs to see the effect of the new value on font-locking." + :type '(repeat string) :group 'Dired-Plus) + +(when (> emacs-major-version 21) ; Emacs 22+ + (defcustom diredp-count-.-and-..-flag nil + "Non-nil means count `.' and `..' when counting files for mode-line." + :type 'boolean :group 'Dired-Plus)) + +;;;###autoload +(defcustom diredp-do-report-echo-limit 5 + "Echo result for each file, for fewer than this many files. +If more than this many files are acted on then there is no echoing. + +Used by some do-and-report commands such as `diredp-do-emacs-command'. +Results that are not echoed are anyway reported by `dired-log', so you +can show them with `?' in the Dired buffer." + :type '(restricted-sexp :match-alternatives (wholenump)) :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-dwim-any-frame-flag pop-up-frames + "*Non-nil means the target directory can be in a window in another frame. +Only visible frames are considered. +This is used by ``dired-dwim-target-directory'. +This option has no effect for Emacs versions before Emacs 22." + :type 'boolean :group 'Dired-Plus) + +(when (fboundp 'dired-hide-details-mode) ; Emacs 24.4+ + (defcustom diredp-hide-details-initially-flag t + "*Non-nil means hide details in Dired from the outset." + :type 'boolean :group 'Dired-Plus + :set (lambda (sym defs) + (custom-set-default sym defs) + (setq diredp-hide-details-last-state diredp-hide-details-initially-flag))) + + (defcustom diredp-hide-details-propagate-flag t + "*Non-nil means display the next Dired buffer the same way as the last. +The last `dired-hide-details-mode' value set is used by the next Dired +buffer created." + :type 'boolean :group 'Dired-Plus)) + +;; Emacs 20 only. +;;;###autoload +(unless (fboundp 'define-minor-mode) + (defcustom diredp-highlight-autofiles-mode t + "*Non-nil means highlight names of files that are autofile bookmarks. +Autofiles that have tags are highlighted using face +`diredp-tagged-autofile-name'. Those with no tags are highlighted +using face `diredp-autofile-name'. + +Setting this option directly does not take effect; use either +\\[customize] or command `diredp-highlight-autofiles-mode'. + +NOTE: When `dired+.el' is loaded (for the first time per Emacs +session), the highlighting is turned ON, regardless of the option +value. To prevent this and have the highlighting OFF by default, you +must do one of the following: + + * Put (diredp-highlight-autofiles-mode -1) in your init file, AFTER + it loads `dired+.el'. + + * Customize the option to `nil', AND ensure that your `custom-file' + (or the `custom-saved-variables' part of your init file) is + evaluated before `dired+.el' is loaded. + +This option has no effect unless you use libraries `Bookmark and +`highlight.el'." + :set (lambda (symbol value) (diredp-highlight-autofiles-mode (if value 1 -1))) + :initialize 'custom-initialize-default + :type 'boolean :group 'Dired-Plus :require 'dired+)) + +;;;###autoload +(defcustom diredp-ignore-compressed-flag t + "*Non-nil means to font-lock names of compressed files as ignored files. +This applies to filenames whose extensions are in +`diredp-compressed-extensions'. If nil they are highlighted using +face `diredp-compressed-file-name'. + +Note: If you change the value of this option then you need to restart +Emacs to see the effect of the new value on font-locking." + :type 'boolean :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-image-preview-in-tooltip (or (and (boundp 'image-dired-thumb-size) image-dired-thumb-size) + 100) + "*Whether and what kind of image preview to show in a tooltip. +The possible values are: + + `nil' : do not show a tooltip preview + integer N>0 : show a thumbnail preview of that size + `full' : show a full-size preview of the image + +To enable tooltip image preview you must turn on `tooltip-mode' and +load library `image-dired.el'. See also option +`diredp-auto-focus-frame-for-thumbnail-tooltip-flag'. + +This option has no effect for Emacs versions prior to Emacs 22." + :type '(choice + (restricted-sexp :tag "Show a thumnail image of size" + :match-alternatives ((lambda (x) (and (wholenump x) (not (zerop x)))))) + (const :tag "Show a full-size image preview" full) + (const :tag "OFF: Do not show an image preview" nil)) + :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-image-show-this-file-use-frame-flag t + "Non-nil means `diredp-image-show-this-file' uses another frame. +If nil then it uses another window. Using another frame means you +have more control over the image size when you use a prefix arg. + +If it uses another window then the prefix arg controls only the +minimum window height, not necessarily the image scale (height). + +\(If the buffer displaying the image is already considered a +special-display buffer by your Emacs setup, then a nil value of this +option has no effect.)" + :type 'boolean :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-list-file-attributes (list '(5 8) 'auto) + "Which file attributes `diredp-list-file' uses, and when." + :group 'Dired-Plus :type '(list (repeat integer) + (choice + (const :tag "Show automatically, immediately" 'auto) + (const :tag "Show on demand via `l'" 'on-demand)))) + +;;;###autoload +(defcustom diredp-max-frames 200 + "*Max number of frames, for commands that find files in separate frames. +These commands are `dired-do-find-marked-files' and +`diredp-do-find-marked-files-recursive'. See their descriptions for +the circumstances in which they show the files in separate frames." + :type '(restricted-sexp :match-alternatives ((lambda (x) (and (wholenump x) (not (zerop x)))))) + :group 'Dired-Plus) + +(when (fboundp 'file-equal-p) ; Emacs 24+ + (defcustom diredp-move-file-dirs () + "Alist of names of files and preferred directories to move them to. +File names should be relative (no directory component). +Target directory names should be absolute." + :group 'files :type '(alist :key-type file :value-type directory))) + +;; (Not used - just use the body directly in the option default value. +;; (defun diredp-omit-files-regexp () +;; "Return regexp to use for font-locking, using `dired-omit-files' as base." +;; (let* ((strg dired-omit-files) +;; (strg (if (eq ?^ (aref strg 0)) (substring strg 1) strg)) ; Remove initial ^ +;; (strg (replace-regexp-in-string "\\(\\\\[|]\\)\\^" "\\1" strg 'FIXEDCASE nil)) ; Remove other ^'s +;; (strg (replace-regexp-in-string "\\([$]\\)" "" strg 'FIXEDCASE nil))) ; Remove $'s +;; strg)) + +(defcustom diredp-omit-files-regexp (let* ((strg dired-omit-files) + (strg (if (eq ?^ (aref strg 0)) ; Remove initial ^ + (substring strg 1) + strg)) + (strg (replace-regexp-in-string "\\(\\\\[|]\\)\\^" ; Remove other ^'s + "\\1" + strg + 'FIXEDCASE + nil)) + (strg (replace-regexp-in-string "\\([$]\\)" ; Remove $'s + "" + strg + 'FIXEDCASE + nil))) + strg) + "Regexp for font-locking file names to be omitted by `dired-omit-mode'. +The regexp is matched only against the file name, but the entire line +is highlighted (with face `diredp-omit-file-name'). + +The default value of this option differs from that of +`dired-omit-files' by removing \"^\" from the beginning, and \"$\" +from the end, of each regexp choice. (The default value of +`dired-omit-files', at least prior to Emacs 27, uses \"^\" and \"$\", +but it should not.) + +If you want to control the beginning and end of choice matches then +use \"\\`\" and \"\\'\" instead of \"^\" and \"$\". + +Note: If you change the value of this option then you need to restart +Emacs to see the effect of the new value on font-locking." + :group 'Dired-Plus :type 'regexp) + +;;;###autoload +(defcustom diredp-prompt-for-bookmark-prefix-flag nil + "*Non-nil means prompt for a prefix string for bookmark names." + :type 'boolean :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-visit-ignore-regexps () + "Regexps matching file names for `diredp-visit-(next|previous)' to skip. +A file or directory name matching one of these regexps is skipped, +along with those with an extension in `diredp-visit-ignore-extensions'." + :type '(repeat regexp) :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-visit-ignore-extensions '("elc") + "Extensions of file names for `diredp-visit-(next|previous)' to skip. +A file name with one of these extensions is skipped, along with those +matching a regexp in `diredp-visit-ignore-regexps'." + :type '(repeat string) :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-w32-local-drives '(("C:" "Local disk")) + "*Local MS Windows drives that you want to use for `diredp-w32-drives'. +Each entry is a list (DRIVE DESCRIPTION), where DRIVE is the drive +name and DESCRIPTION describes DRIVE." + :type '(alist + :key-type (string :tag "Drive name") + :value-type (group (string :tag "Drive description"))) + :group 'Dired-Plus) + +;;;###autoload +(defcustom diredp-wrap-around-flag t + "*Non-nil means Dired \"next\" commands wrap around to buffer beginning." + :type 'boolean :group 'Dired-Plus) + +(when (fboundp 'dired-hide-details-mode) ; Emacs 24.4+ + (defvar diredp-hide-details-last-state diredp-hide-details-initially-flag + "Last `dired-hide-details-mode' value. +Initialized to the value of option `diredp-hide-details-initially-flag'.") + + (defvar diredp-hide-details-toggled nil + "Non-nil means you have already toggled hiding details in this buffer.") + (make-variable-buffer-local 'diredp-hide-details-toggled)) + +;; Same value as the default value of `icicle-re-no-dot'. +(defvar diredp-re-no-dot "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*" + "Regexp that matches anything except `.' and `..'.") + +(defvar diredp-w32-drives-mode-map (let ((map (make-sparse-keymap))) + (define-key map "q" 'bury-buffer) + (define-key map "\r" 'widget-button-press) + (define-key map [mouse-2] 'widget-button-click) + map) + "Keymap for `diredp-w32-drives-mode'.") + +;;; $$$$$$ Starting with Emacs 22, *-move-to* is defvaraliased to *-listing-before*. +;;; But `files+.el' defines *-listing-before*, so we define it here too. +;;; (unless (> emacs-major-version 21) +;;; (defvar directory-listing-before-filename-regexp dired-move-to-filename-regexp +;;; "Regular expression to match up to the file name in a directory listing. +;;; The default value is designed to recognize dates and times +;;; regardless of the language.")) + +;;; Macros + + +;; Unlike `dired-mark-if': +;; +;; 1. Value returned and message indicate both the number matched and the number changed. +;; 2. Added optional arg PLURAL, for irregular plurals (e.g. "directories"). +;; +(defmacro diredp-mark-if (predicate singular &optional plural) + "Mark files for PREDICATE, according to `dired-marker-char'. +PREDICATE is evaluated on each line, with point at beginning of line. +SINGULAR is a singular noun phrase for the type of files being marked. +Optional arg PLURAL is a plural noun phrase for the type of files + being marked. +If PLURAL is nil then SINGULAR should end with a noun that can be +pluralized by adding `s'. + +Return nil if no files matched PREDICATE. +Otherwise return a cons (CHANGED . MATCHED), where: + CHANGED is the number of markings that were changed by the operation. + MATCHED is the number of files that matched PREDICATE." + `(let ((inhibit-read-only t) + changed matched) + (save-excursion + (setq matched 0 + changed 0) + (when ,singular (message "%s %s%s..." + (cond ((eq dired-marker-char ?\040) "Unmarking") + ((eq dired-del-marker dired-marker-char) "Flagging") + (t "Marking")) + (or ,plural (concat ,singular "s")) + (if (eq dired-del-marker dired-marker-char) " for deletion" ""))) + (goto-char (point-min)) + (while (not (eobp)) + (when ,predicate + (setq matched (1+ matched)) + (unless (eq dired-marker-char (char-after)) + (delete-char 1) (insert dired-marker-char) (setq changed (1+ changed)))) + (forward-line 1)) + (when ,singular (message "%s %s%s%s newly %s%s" + matched + (if (= matched 1) ,singular (or ,plural (concat ,singular "s"))) + (if (not (= matched changed)) " matched, " "") + (if (not (= matched changed)) changed "") + (if (eq dired-marker-char ?\040) "un" "") + (if (eq dired-marker-char dired-del-marker) "flagged" "marked")))) + (and (> matched 0) (cons changed matched)))) + + +;; Just a helper function for `dired-map-over-marks'. +(defun diredp-get-file-or-dir-name (arg) + "Return name of next file or directory or nil if none. +Argument ARG: + `all-files-no-dirs' or nil means skip directories. + `all-files-no-dots' means skip `.' and `..'." + (let ((fname nil)) + (while (and (not fname) (not (eobp))) + (setq fname (dired-get-filename t t)) + (when (and fname (or (not arg) (eq arg 'all-files-no-dirs)) (file-directory-p fname)) + (setq fname nil)) + (when (and fname (eq arg 'all-files-no-dots) (or (member fname '("." "..")) + (diredp-string-match-p "/\\.\\.?$" fname))) + (setq fname nil)) + (forward-line 1)) + (forward-line -1) + fname)) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; Treat multiple `C-u' specially. +;; +(defmacro dired-map-over-marks (body arg &optional show-progress + distinguish-one-marked) + "Eval BODY with point on each marked line. Return a list of BODY's results. +If no marked file could be found, execute BODY on the current line. +ARG, if non-nil, specifies the files to use instead of the marked files. + If ARG is an integer, use the next ARG files (previous -ARG, if < 0). + In that case, point is dragged along. This is so that commands on + the next ARG (instead of the marked) files can be easily chained. + If ARG is a cons with element 16, 64, or 256, corresponding to + `C-u C-u', `C-u C-u C-u', or `C-u C-u C-u C-u', then use all files + in the Dired buffer, where: + 16 includes NO directories (including `.' and `..') + 64 includes directories EXCEPT `.' and `..' + 256 includes ALL directories (including `.' and `..') + If ARG is otherwise non-nil, use the current file. +If optional third arg SHOW-PROGRESS evaluates to non-nil, + redisplay the Dired buffer after each file is processed. + +No guarantee is made about the position on the marked line. BODY must +ensure this itself, if it depends on this. + +Search starts at the beginning of the buffer, thus the car of the list +corresponds to the line nearest the end of the buffer. This is also +true for (positive and negative) integer values of ARG. + +BODY should not be too long, because it is expanded four times. + +If DISTINGUISH-ONE-MARKED is non-nil, then return (t FILENAME) instead + of (FILENAME), if only one file is marked." + ;; WARNING: BODY must not add new lines before point - this may cause an + ;; endless loop. This warning should not apply any longer, sk 2-Sep-1991 14:10. + `(prog1 + (let ((inhibit-read-only t) + (newarg ,arg) + multi-C-u case-fold-search found results) + (when (and (consp newarg) (> (prefix-numeric-value newarg) 4)) + (setq newarg (case (prefix-numeric-value newarg) + (16 'all-files-no-dirs) ; `C-u C-u' + (64 'all-files-no-dots) ; `C-u C-u C-u' + (256 'all-files) ; `C-u C-u C-u C-u' + (t 'all-files-no-dirs)) + multi-C-u t)) + (if (and newarg (not multi-C-u)) + (if (integerp newarg) + (progn ; No `save-excursion', want to move point. + (dired-repeat-over-lines newarg #'(lambda () + (when ,show-progress (sit-for 0)) + (setq results (cons ,body results)))) + (if (< newarg 0) (nreverse results) results)) + ;; Non-nil, non-integer ARG means use current file: + (list ,body)) + (let ((regexp (dired-marker-regexp)) + next-position) + (save-excursion + (goto-char (point-min)) + ;; Remember position of next marked file before BODY can insert lines before the + ;; just found file, confusing us by finding the same marked file again and again... + (setq next-position (and (if multi-C-u + (diredp-get-file-or-dir-name newarg) + (re-search-forward regexp nil t)) + (point-marker)) + found (not (null next-position))) + (while next-position + (goto-char next-position) + (when ,show-progress (sit-for 0)) + (setq results (cons ,body results)) + ;; move after last match + (goto-char next-position) + (forward-line 1) + (set-marker next-position nil) + (setq next-position (and (if multi-C-u + (diredp-get-file-or-dir-name newarg) + (re-search-forward regexp nil t)) + (point-marker))))) + (when (and ,distinguish-one-marked (= (length results) 1)) + (setq results (cons t results))) + (if found results (list ,body))))) + ;; `save-excursion' loses, again + (dired-move-to-filename))) + +;; Same as `icicle-with-help-window' in `icicles-mac.el' +;; and `bmkp-with-help-window' in `bookmark+-mac.el'. +(defmacro diredp-with-help-window (buffer &rest body) + "`with-help-window', if available; else `with-output-to-temp-buffer'." + (if (fboundp 'with-help-window) + `(with-help-window ,buffer ,@body) + `(with-output-to-temp-buffer ,buffer ,@body))) + +(put 'diredp-with-help-window 'common-lisp-indent-function '(4 &body)) + +;;; Utility functions + +;; Same as `imenup-delete-if-not'. +;; +(defun diredp-delete-if-not (predicate xs) + "Remove all elements of list XS that do not satisfy PREDICATE. +This operation is destructive, reusing conses of XS whenever possible." + (while (and xs (not (funcall predicate (car xs)))) + (setq xs (cdr xs))) + (let ((cl-p xs)) + (while (cdr cl-p) + (if (not (funcall predicate (cadr cl-p))) (setcdr cl-p (cddr cl-p)) (setq cl-p (cdr cl-p))))) + xs) + +;; Same as `imenup-delete-if'. +;; +(defun diredp-delete-if (predicate xs) + "Remove all elements of list XS that satisfy PREDICATE. +This operation is destructive, reusing conses of XS whenever possible." + (while (and xs (funcall predicate (car xs))) + (setq xs (cdr xs))) + (let ((cl-p xs)) + (while (cdr cl-p) + (if (funcall predicate (cadr cl-p)) + (setcdr cl-p (cddr cl-p)) + (setq cl-p (cdr cl-p))))) + xs) + +;; Same as `tap-string-match-p' in `thingatpt+.el'. +(if (fboundp 'string-match-p) + (defalias 'diredp-string-match-p 'string-match-p) ; Emacs 23+ + (defun diredp-string-match-p (regexp string &optional start) + "Like `string-match', but this saves and restores the match data." + (save-match-data (string-match regexp string start)))) + +(if (fboundp 'looking-at-p) + (defalias 'diredp-looking-at-p 'looking-at-p) ; Emacs 23+ + (defun diredp-looking-at-p (regexp) + "Like `looking-at', but this saves and restores the match data." + (save-match-data (looking-at regexp)))) + +;; `dired-read-regexp' does not accept DEFAULT and HISTORY for older Emacsen, so use this. +(defun diredp-read-regexp (prompt &optional default history) + "Read a regexp. +HISTORY defaults to `dired-regexp-history'." + (setq history (or history 'dired-regexp-history)) + (if (fboundp 'read-regexp) + (read-regexp prompt default history) + (read-from-minibuffer prompt nil nil nil history default))) + +(if (fboundp 'delete-dups) + (defalias 'diredp-delete-dups 'delete-dups) + (defun diredp-delete-dups (list) + "Destructively remove `equal' duplicates from LIST. +Store the result in LIST and return it. LIST must be a proper list. +Of several `equal' occurrences of an element in LIST, the first +one is kept." + (let ((tail list)) + (while tail + (setcdr tail (delete (car tail) (cdr tail))) + (setq tail (cdr tail)))) + list)) + +(defun diredp-nonempty-region-p () + "Return non-nil if region is active and non-empty." + (and transient-mark-mode mark-active (mark) (> (region-end) (region-beginning)))) + +(defun diredp-get-image-filename (&optional localp no-error-if-not-filep) + "Return the image-file name on this line, or nil if no image file. +If not in Dired (or a mode derived from Dired), then test the entire +text of the current line as the file name. + +The optional args are the same as for `dired-get-filename'. They are +ignored if not in a Dired mode. + +\(Prior to Emacs 22, this function just returns nil.)" + (let ((file (if (derived-mode-p 'dired-mode) + (dired-get-filename localp no-error-if-not-filep) + ;; Make it work also for `diredp-list-files' listings. + (buffer-substring-no-properties (line-beginning-position) (line-end-position))))) + (and file + (fboundp 'image-file-name-regexp) ; Emacs 22+, `image-file.el'. + (diredp-string-match-p (image-file-name-regexp) file) + file))) + +(defun diredp-root-directory-p (file) + "Return non-nil if FILE is a root directory." + (if (fboundp 'ange-ftp-root-dir-p) + (ange-ftp-root-dir-p (file-name-as-directory file)) + ;; This is essentially `ange-ftp-root-dir-p' applied to `file-name-as-directory'. + ;; If `ange-ftp-root-dir-p' changes, update this code. + (or (and (eq system-type 'windows-nt) (diredp-string-match-p "\\`[a-zA-Z]:[/\\]\\'" + (file-name-as-directory file))) + (string= "/" file)))) + +(defun diredp-parent-dir (file &optional relativep) + "Return the parent directory of FILE, or nil if none. +Optional arg RELATIVEP non-nil means return a relative name, that is, +just the parent component." + (let ((parent (file-name-directory (directory-file-name (expand-file-name file)))) + relparent) + (when relativep (setq relparent (file-name-nondirectory (directory-file-name parent)))) + (and (not (equal parent file)) (or relparent parent)))) + +(unless (fboundp 'derived-mode-p) ; Emacs 20, 21. + (defun derived-mode-p (&rest modes) + "Non-nil if the current major mode is derived from one of MODES. +Uses the `derived-mode-parent' property of the symbol to trace backwards." + (let ((parent major-mode)) + (while (and (not (memq parent modes)) (setq parent (get parent 'derived-mode-parent)))) + parent))) + +(defun diredp-ensure-mode () + "Raise an error if not in Dired or a mode derived from it." + (unless (derived-mode-p 'dired-mode) + (error "You must be in Dired or a mode derived from it to use this command"))) + +(defun diredp-ensure-bookmark+ () + (unless (require 'bookmark+ nil t) (error "This command requires library `bookmark+.el'"))) + + +(unless (fboundp 'dired-nondirectory-p) ; Emacs 20, 21. + (defun dired-nondirectory-p (file) + "Return non-nil if FILE is not a directory." + (not (file-directory-p file)))) + + +;;; Some of the redefinitions that follow are essentially unaltered vanilla Emacs code to be +;;; reloaded, to use the new definition of `dired-map-over-marks' here. + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; 1. Pass non-nil second arg to `dired-get-filename' so we can include `.' and `..'. +;; 2. Doc string is updated to reflect the new ARG behavior. +;; 3. Allow, unlike vanilla Emacs, use of FILTER and DISTINGUISH-ONE-MARKED together. +;; +(defun dired-get-marked-files (&optional localp arg filter distinguish-one-marked error-if-none-p) + "Return names of the marked files and directories as a list of strings. +The list is in the same order as the buffer, that is, the car is the + first marked file. +Values returned are normally absolute file names. +Optional arg LOCALP as in `dired-get-filename'. +Optional second argument ARG specifies files to use instead of marked. + Usually ARG comes from the command's prefix arg. + If ARG is an integer, use the next ARG files (previous -ARG, if < 0). + If ARG is a cons with element 16, 64, or 256, corresponding to + `C-u C-u', `C-u C-u C-u', or `C-u C-u C-u C-u', then use all files + in the Dired buffer, where: + 16 includes NO directories (including `.' and `..') + 64 includes directories EXCEPT `.' and `..' + 256 includes ALL directories (including `.' and `..') + If ARG is otherwise non-nil, use the current file. +Optional third argument FILTER, if non-nil, is a function to select + some of the files: those for which (funcall FILTER FILENAME) is + non-nil. +If DISTINGUISH-ONE-MARKED is non-nil, then return (t FILENAME) instead + of (FILENAME) if only one file is marked (after any filtering by + FILTER). +If ERROR-IF-NONE-P is non-nil, signal an error if the list of files is + empty. If ERROR-IF-NONE-P is a string then it is the error message. + +Note that the Dired+ version of this function differs from the vanilla +version in these respects: + +* There are more possibilities for argument ARG (prefix argument). +* Directories `.' and `..' can be included as marked. +* You can use arguments FILTER and DISTINGUISH-ONE-MARKED together." + (let ((all (delq nil (save-excursion (dired-map-over-marks (dired-get-filename localp 'NO-ERROR-IF-NOT-FILEP) + arg + nil + distinguish-one-marked)))) + result) + (when (equal all '(t)) (setq all nil)) ; Added by vanilla Emacs 24+. + (if (and distinguish-one-marked (eq (car all) t)) + (if (not filter) + all + (and (funcall filter (cadr all)) (list t (cadr all)))) + (dolist (file all) + (when (or (not filter) (funcall filter file)) (push file result))) + (when (and (null result) error-if-none-p) + (diredp-user-error (if (stringp error-if-none-p) error-if-none-p "No files specified"))) + result))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +;; 1. Define here to make use of my `dired-map-over-marks'. +;; 2. Added &rest arg FUN-ARGS. +;; 3. Added doc string. +;; +(defun dired-map-over-marks-check (fun mark-arg op-symbol &optional show-progress &rest fun-args) + "Map FUN over marked lines and display failures. +FUN returns non-nil (the offending object, e.g. the short form of the +filename) for a failure and probably logs a detailed error explanation +using function `dired-log'. + +MARK-ARG is as the second argument of `dired-map-over-marks'. + +OP-SYMBOL is a symbol describing the operation performed (e.g. +`compress'). It is used with `dired-mark-pop-up' to prompt the user +\(e.g. with `Compress * [2 files]? ') and to display errors (e.g. +`Failed to compress 1 of 2 files - type ? for details (\"foo\")') + +SHOW-PROGRESS if non-nil means redisplay Dired after each file. + +FUN-ARGS is the list of any remaining args to +`dired-map-over-marks-check'. Function FUN is applied to these +arguments." + (and (dired-mark-confirm op-symbol mark-arg) + (let* ((results (dired-map-over-marks (apply fun fun-args) mark-arg show-progress)) ; FUN return vals. + (nb-results (length results)) + (failures (delq nil results)) + (nb-fail (length failures)) + (op-strg (if (eq op-symbol 'compress) "Compress or uncompress" (capitalize + (symbol-name op-symbol))))) + (if (null failures) + (message "%s: %d file%s." op-strg nb-results (dired-plural-s nb-results)) + (dired-log-summary (format "Failed to %s %d of %d file%s" + (downcase op-strg) nb-fail nb-results (dired-plural-s nb-results)) + failures))))) + +;; Like `dired-map-over-marks-check', but `dired-log-summary' is always called, and first arg passed is different. +;; +(defun diredp-map-over-marks-and-report (fun mark-arg op-symbol &optional show-progress &rest fun-args) + "Map FUN over marked lines and report the results. +FUN returns non-nil (the offending object, e.g. the short form of the +filename) for a failure and probably logs a detailed error explanation +using function `dired-log'. + +MARK-ARG is as the second argument of `dired-map-over-marks'. + +OP-SYMBOL is a symbol describing the operation performed (e.g. +`compress'). It is used with `dired-mark-pop-up' to prompt the user +\(e.g. with `Compress * [2 files]? ') and to display errors (e.g. +`Failed to compress 1 of 2 files - type ? to see why (\"foo\")') + +SHOW-PROGRESS if non-nil means redisplay Dired after each file. + +FUN-ARGS is the list of any remaining args to +`diredp-map-over-marks-and-report'. Function FUN is applied to these +arguments." + (and (dired-mark-confirm op-symbol mark-arg) + (let* ((results (dired-map-over-marks (apply fun fun-args) mark-arg show-progress)) ; FUN return vals. + (nb-results (length results)) + (failures (delq nil results)) + (nb-fail (length failures)) + (op-strg (capitalize (symbol-name op-symbol)))) + (dired-log-summary (format "%s for %d file%s%s" + op-strg nb-results (dired-plural-s nb-results) + (if failures (format ": %d failures" nb-fail) "")) + failures)))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +(when (boundp 'dired-subdir-switches) ; Emacs 22+ + (defun dired-do-redisplay (&optional arg test-for-subdir) ; Bound to `l' + "Redisplay all marked (or next ARG) files. +If on a subdir line, redisplay that subdirectory. In that case, +a prefix arg lets you edit the `ls' switches used for the new listing. + +Dired remembers switches specified with a prefix arg, so that reverting +the buffer will not reset them. However, using `dired-undo' to re-insert +or delete subdirectories can bypass this machinery. Hence, you sometimes +may have to reset some subdirectory switches after a `dired-undo'. +You can reset all subdirectory switches to the default using +\\\\[dired-reset-subdir-switches]. +See Info node `(emacs)Subdir switches' for more details." + ;; Moves point if the next ARG files are redisplayed. + (interactive "P\np") + (if (and test-for-subdir (dired-get-subdir)) + (let* ((dir (dired-get-subdir)) + (switches (cdr (assoc-string dir dired-switches-alist)))) + (dired-insert-subdir dir (and arg (read-string "Switches for listing: " + (or switches + dired-subdir-switches + dired-actual-switches))))) + (message "Redisplaying...") + ;; `message' is much faster than making `dired-map-over-marks' show progress + (dired-uncache (if (consp dired-directory) (car dired-directory) dired-directory)) + (dired-map-over-marks (let ((fname (dired-get-filename)) + ;; Postpone readin hook map over all marked files (Bug#6810). + (dired-after-readin-hook nil)) + (message "Redisplaying... `%s'" fname) + (dired-update-file-line fname)) + arg) + (run-hooks 'dired-after-readin-hook) + (dired-move-to-filename) + (message "Redisplaying...done")))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +(unless (boundp 'dired-subdir-switches) ; Emacs 20, 21 + (defun dired-do-redisplay (&optional arg test-for-subdir) ; Bound to `l' + "Redisplay all marked (or next ARG) files. +If on a subdir line, redisplay that subdirectory. In that case, +a prefix arg lets you edit the `ls' switches used for the new listing." + ;; Moves point if the next ARG files are redisplayed. + (interactive "P\np") + (if (and test-for-subdir (dired-get-subdir)) + (dired-insert-subdir (dired-get-subdir) + (and arg (read-string "Switches for listing: " dired-actual-switches))) + (message "Redisplaying...") + ;; `message' is much faster than making dired-map-over-marks show progress + (dired-uncache (if (consp dired-directory) (car dired-directory) dired-directory)) + (dired-map-over-marks (let ((fname (dired-get-filename))) + (message "Redisplaying... `%s'" fname) + (dired-update-file-line fname)) + arg) + (dired-move-to-filename) + (message "Redisplaying...done")))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +(when (fboundp 'get-window-with-predicate) ; Emacs 22+ + (defun dired-dwim-target-directory () + "Guess a target directory to use for Dired. +If there is a Dired buffer displayed in another window, use its +current subdir, else use current subdir of this Dired buffer." + (let ((this-dir (and (eq major-mode 'dired-mode) (dired-current-directory)))) + ;; Non-dired buffer may want to profit from this function, e.g. `vm-uudecode'. + (if dired-dwim-target + (let* ((other-win (get-window-with-predicate (lambda (window) + (with-current-buffer (window-buffer window) + (eq major-mode 'dired-mode))) + nil + (and diredp-dwim-any-frame-flag 'visible))) + (other-dir (and other-win (with-current-buffer (window-buffer other-win) + (and (eq major-mode 'dired-mode) (dired-current-directory)))))) + (or other-dir this-dir)) + this-dir)))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; 1. Added behavior for non-positive prefix arg: +;; * Construct a cons DIRNAME arg. +;; * Read a Dired buffer name (not a directory) for its car. +;; * If READ-EXTRA-FILES-P is non-nil then read any number of file and dir names, to be included as its cdr. +;; * If chosen Dired buffer exists and is an ordinary listing then start out with its `directory-files'. +;; +;; 2. If you use Icicles then this is a multi-command - see doc for `dired' defadvice. +;; +(defun dired-read-dir-and-switches (string &optional read-extra-files-p dired-buffer) + "Read arguments for `dired' commands. +STRING is added to the prompt after \"Dired \". If not \"\", it should +end with a space. + +With a non-negative prefix arg, read the `ls' switches. +With a non-negative prefix arg or none, read the directory to Dired. + +With a non-positive prefix arg: +* If DIRED-BUFFER is non-nil, it is the name of the Dired buffer to + use. Otherwise, read it (it is not necessarily a directory name). + If in Dired now, the current buffer name is the default. +* If READ-EXTRA-FILES-P is non-nil then read any number of directory + or file names, to make up the Dired arbitrary-files listing. You + can use file-name wildcards (i.e., `*' for globbing), to include the + matching files and directories. Use `C-g' when done entering the + files and directories to list. + +Return a list of arguments for `dired': (DIRNAME SWITCHES). DIRNAME +here has the same form as `dired-directory'. When a non-positive +prefix arg is used, DIRNAME is a cons of the buffer name and the list +of file names. + +If you use Icicles then reading uses Icicles completion, with +additional multi-command keys. See `dired' (defadvice doc)." + (let* ((switchs (and current-prefix-arg + (natnump (prefix-numeric-value current-prefix-arg)) + (read-string "Dired listing switches: " + dired-listing-switches))) + (icicle-candidate-action-fn + (lambda (cand) + (dired-other-window cand (and current-prefix-arg (read-string "Dired listing switches: " + dired-listing-switches))) + (select-window (minibuffer-window)) + (select-frame-set-input-focus (selected-frame)))) +;;; $$$$$$ Alternative: Could choose no-op for non-dir candidate. +;;; (icicle-candidate-action-fn +;;; (lambda (cand) +;;; (cond ((file-directory-p cand) +;;; (dired-other-window cand (and current-prefix-arg (read-string "Dired listing switches: " +;;; dired-listing-switches))) +;;; (select-window (minibuffer-window)) +;;; (select-frame-set-input-focus (selected-frame))) +;;; (t +;;; (message "Not a directory: `%s'" cand) (sit-for 2))))) + (icicle-all-candidates-list-alt-action-fn ; M-|' + (lambda (files) + (let ((enable-recursive-minibuffers t)) + (dired-other-window (cons (read-string (format "Dired %s(buffer name): " string)) files))))) + (icicle-sort-comparer (or (and (boundp 'icicle-file-sort) ; If not reading files + icicle-file-sort) ; then dirs first. + (and (> (prefix-numeric-value current-prefix-arg) 0) + 'icicle-dirs-first-p) + (and (boundp 'icicle-sort-comparer) + icicle-sort-comparer))) + + ;; The rest of the bindings are from `icicle-file-bindings', in `icicles-mac.el'. + (completion-ignore-case + (or (and (boundp 'read-file-name-completion-ignore-case) read-file-name-completion-ignore-case) + completion-ignore-case)) + (icicle-show-Completions-initially-flag (and (boundp 'icicle-show-Completions-initially-flag) + (or icicle-show-Completions-initially-flag + icicle-files-ido-like-flag))) + (icicle-top-level-when-sole-completion-flag (and (boundp 'icicle-top-level-when-sole-completion-flag) + (or icicle-top-level-when-sole-completion-flag + icicle-files-ido-like-flag))) + (icicle-default-value (and (boundp 'icicle-default-value) + (if (and icicle-files-ido-like-flag + icicle-default-value) + icicle-files-ido-like-flag + ;; Get default via `M-n', but do not insert it. + (and (memq icicle-default-value '(t nil)) + icicle-default-value)))) + (icicle-must-match-regexp (and (boundp 'icicle-file-match-regexp) + icicle-file-match-regexp)) + (icicle-must-not-match-regexp (and (boundp 'icicle-file-no-match-regexp) + icicle-file-no-match-regexp)) + (icicle-must-pass-after-match-predicate (and (boundp 'icicle-file-predicate) + icicle-file-predicate)) + (icicle-require-match-flag (and (boundp 'icicle-file-require-match-flag) + icicle-file-require-match-flag)) + (icicle-file-completing-p t) + (icicle-extra-candidates (and (boundp 'icicle-file-extras) icicle-file-extras)) + (icicle-transform-function 'icicle-remove-dups-if-extras) + ;; Put `icicle-file-sort' first. If already in the list, move it, else add it, to beginning. + (icicle--temp-orders (and (boundp 'icicle-sort-orders-alist) + (copy-sequence icicle-sort-orders-alist))) + (icicle-candidate-help-fn (lambda (cand) + (icicle-describe-file cand current-prefix-arg t))) + (icicle-candidate-alt-action-fn (and (boundp 'icicle-candidate-alt-action-fn) + (or icicle-candidate-alt-action-fn + (icicle-alt-act-fn-for-type "file")))) + (icicle-delete-candidate-object 'icicle-delete-file-or-directory) + (icicle-sort-orders-alist + (and (boundp 'icicle-sort-orders-alist) + (progn (when t ; $$$$ (and icicle-file-sort-first-time-p icicle-file-sort) + (setq icicle-sort-comparer icicle-file-sort)) + ; $$$$ (setq icicle-file-sort-first-time-p nil)) + (if icicle-file-sort + (let ((already-there (rassq icicle-file-sort icicle--temp-orders))) + (if already-there + (cons already-there (setq icicle--temp-orders (delete already-there + icicle--temp-orders))) + (cons `("by `icicle-file-sort'" ,@icicle-file-sort) icicle--temp-orders))) + icicle--temp-orders))))) + (when (fboundp 'icicle-bind-file-candidate-keys) (icicle-bind-file-candidate-keys)) + (unwind-protect + (list + (if (> (prefix-numeric-value current-prefix-arg) 0) + ;; If a dialog box is about to be used, call `read-directory-name' so the dialog + ;; code knows we want directories. Some dialog boxes can only select directories + ;; or files when popped up, not both. If no dialog box is used, call `read-file-name' + ;; because the user may want completion of file names for use in a wildcard pattern. + (funcall (if (and (fboundp 'read-directory-name) (next-read-file-uses-dialog-p)) + #'read-directory-name + #'read-file-name) + (format "Dired %s(directory): " string) nil default-directory nil) + (dolist (db dired-buffers) ; Remove any killed buffers from `dired-buffers' (even if DIRED-BUFFER). + (unless (buffer-name (cdr db)) (setq dired-buffers (delq db dired-buffers)))) + (let* ((dbufs (and (not dired-buffer) + (mapcar (lambda (db) (list (buffer-name (cdr db)))) dired-buffers))) + (dirbuf (or dired-buffer + (completing-read (format "Dired %s(buffer name): " string) dbufs nil nil nil nil + (and (derived-mode-p 'dired-mode) (buffer-name))))) + (files (and (diredp-existing-dired-buffer-p dirbuf) + (with-current-buffer (get-buffer dirbuf) + (and (not (consp dired-directory)) + (directory-files dired-directory 'FULL diredp-re-no-dot))))) + file) + (when read-extra-files-p + (while (condition-case nil ; Use lax completion, to allow wildcards. + (setq file (read-file-name "File or dir (C-g when done): ")) + (quit nil)) + ;; Do not allow root dir (`/' or a Windows drive letter, e.g. `d:/'). + (if (diredp-root-directory-p file) + (progn (message "Cannot choose root directory") (sit-for 1)) + (push file files)))) + (cons dirbuf files))) + switchs) + (when (fboundp 'icicle-unbind-file-candidate-keys) (icicle-unbind-file-candidate-keys))))) + + +;;; $$$$$$$$ An alternative implementation - different behavior. +;;; +;;; ;; REPLACE ORIGINAL in `dired.el'. +;;; ;; +;;; ;; Non-positive prefix arg means construct cons DIRNAME arg: Read Dired name and files/dirs. +;;; ;; +;;; (defun dired-read-dir-and-switches (string) +;;; "Read arguments for `dired'. +;;; With a non-negative prefix arg, prompt first for `ls' switches. +;;; With a non-positive prefix arg, read the Dired buffer name and then +;;; read any number of dir or file names, to make up the Dired listing. + +;;; STRING is appended to the prompt, unless prefix arg is non-positive. +;;; If non-empty, STRING should begin with a SPC." +;;; (let ((switches (and current-prefix-arg +;;; (>= (prefix-numeric-value current-prefix-arg) 0) +;;; (read-string "Dired listing switches: " dired-listing-switches))) +;;; (formt (format "Dired %s(directory): " string)) +;;; (entries ()) +;;; (curr-entry "")) +;;; (when (and current-prefix-arg (<= (prefix-numeric-value current-prefix-arg) 0)) +;;; (push (completing-read "Dired buffer name: " dired-buffers) entries) +;;; (setq curr-entry (read-file-name (format "Dir or file: ") nil "" 'MUST-MATCH)) +;;; (while (not (equal "" curr-entry)) +;;; (push curr-entry entries) +;;; (setq curr-entry (read-file-name (format "Dir or file: ") nil "" 'MUST-MATCH))) +;;; (unless (cadr entries) (push default-directory entries))) +;;; (list (or (nreverse entries) (if (and (fboundp 'next-read-file-uses-dialog-p) +;;; (next-read-file-uses-dialog-p)) +;;; (read-directory-name formt nil default-directory nil) +;;; (read-file-name formt nil default-directory nil))) +;;; switches))) + + +;; ADVISE ORIGINAL in `dired.el'. +;; +;; Add to doc string, to document non-positive prefix arg. +;; +(defadvice dired (before diredp-doc-cons-arg activate) + "Interactively, a prefix argument changes the behavior as follows: + +* If >= 0, you are first prompted for the `ls' switches to use. + +* If <= 0, you are prompted first for the name of the Dired buffer. + Then you are prompted repeatedly for the names of the directories + or files to list in the buffer. You can use file-name wildcards + (i.e., `*' for globbing), to include the matching files and + directories. Use `C-g' to end. + + In other words, instead of listing a single directory, the Dired + buffer can list any number of directories and file names, which can + even belong to different directory trees. + +The rest of this description applies only if you use Icicles. + +In Icicle mode this is a multi-command: You can cycle among file-name +completion candidates and act individually on those that name +directories. The action is to open Dired for the directory. While +cycling, these keys are active: + +\\\ +`C-mouse-2', `C-return' - Act on current completion candidate only +`C-down', `C-wheel-down' - Move to next completion candidate and act +`C-up', `C-wheel-up' - Move to previous completion candidate and act +`C-next' - Move to next apropos-completion candidate and act +`C-prior' - Move to previous apropos-completion candidate and act +`C-end' - Move to next prefix-completion candidate and act +`C-home' - Move to previous prefix-completion candidate and act +`\\[icicle-all-candidates-action]' - Act on *all* candidates, successively (careful!) +`\\[icicle-all-candidates-list-alt-action]' - Open Dired on all candidates + +When candidate action and cycling are combined (e.g. `C-next'), user +option `icicle-act-before-cycle-flag' determines which occurs first. + +With prefix `C-M-' instead of `C-', the same keys (`C-M-mouse-2', +`C-M-RET', `C-M-down', and so on) provide help about candidates. + +Use `mouse-2', `RET', or `S-RET' to finally choose a candidate, or +`C-g' to quit. + +These keys are also bound in the minibuffer during completion (`*' +means the key requires library `Bookmark+'): + + S-delete - Delete candidate file or (empty) dir + C-c + - Create a new directory + C-backspace - Go up one directory level + * C-x C-t * - Narrow to files with all of the tags you specify + * C-x C-t + - Narrow to files with some of the tags you specify + * C-x C-t % * - Narrow to files with all tags matching a regexp + * C-x C-t % + - Narrow to files with some tags matching a regexp + * C-x a + - Add tags to the current-candidate file + * C-x a - - Remove tags from the current-candidate file + * C-x m - Access file bookmarks (not just autofiles)" + (interactive (dired-read-dir-and-switches "" 'READ-EXTRA-FILES-P))) + + +;; ADVISE ORIGINAL in `dired.el'. +;; +;; Add to doc string, to document non-positive prefix arg. +;; +(defadvice dired-other-window (before diredp-doc-cons-arg activate) + "Interactively, a prefix argument changes the behavior. +A non-positive prefix arg lets you choose an explicit set of files and +directories to list. See the advice for `dired' for more information." + (interactive (dired-read-dir-and-switches "" 'READ-EXTRA-FILES-P))) + + +;; ADVISE ORIGINAL in `dired.el'. +;; +;; Add to doc string, to document non-positive prefix arg. +;; +(defadvice dired-other-frame (before diredp-doc-cons-arg activate) + "Interactively, a prefix argument changes the behavior. +A non-positive prefix arg lets you choose an explicit set of files and +directories to list. See the advice for `dired' for more information." + (interactive (dired-read-dir-and-switches "" 'READ-EXTRA-FILES-P))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; Made compatible with Emacs 20, 21, which do not have [:alnum]. +;; Also, this is defined here because it is used elsewhere in the file. +;; +(defun dired-switches-escape-p (switches) + "Return non-nil if the string SWITCHES contains `-b' or `--escape'." + (if (fboundp 'dired-switches-check) ; Emacs 24.4+ - see Emacs bug #17218. + (dired-switches-check switches "escape" "b") + ;; Do not match things like "--block-size" that happen to contain "b". + (if (> emacs-major-version 21) ; SWITCHES must be a string here, not nil. + (diredp-string-match-p "\\(\\`\\| \\)-[[:alnum:]]*b\\|--escape\\>" switches) + (diredp-string-match-p "\\(\\`\\| \\)-\\(\w\\|[0-9]\\)*b\\|--escape\\>" switches)))) + + +;; From `dired.el' + +(when (and (> emacs-major-version 22) (featurep 'ls-lisp+)) + +;;; 2012/04/26: Commented this out. +;;; Might need it again when update `ls-lisp+.el' to fix other things. +;;; +;;; ;; Use t as WILDCARD arg to `dired-insert-directory'. +;;; ;; +;;; (defun dired-readin-insert () +;;; ;; Insert listing for the specified dir (and maybe file list) +;;; ;; already in dired-directory, assuming a clean buffer. +;;; (let (dir file-list) +;;; (if (consp dired-directory) +;;; (setq dir (car dired-directory) +;;; file-list (cdr dired-directory)) +;;; (setq dir dired-directory +;;; file-list ())) +;;; (setq dir (expand-file-name dir)) +;;; (if (and (equal "" (file-name-nondirectory dir)) (not file-list)) +;;; ;; If we are reading a whole single directory... +;;; (dired-insert-directory dir dired-actual-switches nil nil t) +;;; (unless (file-readable-p (directory-file-name (file-name-directory dir))) +;;; (error "Directory `%s' inaccessible or nonexistent" dir)) +;;; ;; Else treat it as a wildcard spec. +;;; (dired-insert-directory dir dired-actual-switches file-list t t)))) + + + ;; REPLACE ORIGINAL in `dired.el'. + ;; + ;; Compute WILDCARD arg for `insert-directory' for individual file (don't just use nil). + ;; + (defun dired-insert-directory (dir switches &optional file-list wildcard hdr) + "Insert a directory listing of DIR, Dired style. +Use SWITCHES to make the listings. +If FILE-LIST is non-nil, list only those files. +Otherwise, if WILDCARD is non-nil, expand wildcards; + in that case, DIR should be a file name that uses wildcards. +In other cases, DIR should be a directory name or a directory filename. +If HDR is non-nil, insert a header line with the directory name." + (let ((opoint (point)) + (process-environment (copy-sequence process-environment)) + end) + (when (and + ;; Do not try to invoke `ls' if on DOS/Windows, where `ls-lisp' is used, unless + ;; the user really wants to use `ls', as indicated by + ;; `ls-lisp-use-insert-directory-program'. + (or (not (featurep 'ls-lisp)) ls-lisp-use-insert-directory-program) + (or (if (eq dired-use-ls-dired 'unspecified) + ;; Check if "ls --dired" gives exit code 0. Put it in `dired-use-ls-dired'. + (or (setq dired-use-ls-dired (eq 0 (call-process insert-directory-program + nil nil nil "--dired"))) + (progn (message "Command `ls' does not support switch `--dired' - see \ +`dired-use-ls-dired'.") + nil)) + dired-use-ls-dired) + (file-remote-p dir))) + (setq switches (concat "--dired " switches))) + ;; We used to specify the C locale here, to force English month names. This should not be + ;; necessary any more with the new value of `directory-listing-before-filename-regexp'. + (if file-list + (dolist (f file-list) + (let ((beg (point))) + ;; Compute wildcard arg for this file. + (insert-directory f switches (diredp-string-match-p "[[?*]" f) nil) + ;; Re-align fields, if necessary. + (dired-align-file beg (point)))) + (insert-directory dir switches wildcard (not wildcard))) + ;; Quote certain characters, unless `ls' quoted them for us. + (unless (dired-switches-escape-p dired-actual-switches) + (save-excursion + (setq end (point-marker)) + (goto-char opoint) + (while (search-forward "\\" end t) + (replace-match (apply #'propertize "\\\\" (text-properties-at (match-beginning 0))) + nil t)) + (goto-char opoint) + (while (search-forward "\^m" end t) + (replace-match (apply #'propertize "\\015" (text-properties-at (match-beginning 0))) + nil t)) + (set-marker end nil)) + ;; Comment in original, from some Emacs Dev developer: + ;; + ;; Replace any newlines in DIR with literal "\n" for the sake of the header line. To + ;; disambiguate a literal "\n" in the actual dirname, we also replace "\" with "\\". + ;; I think this should always be done, irrespective of the value of + ;; dired-actual-switches, because: + ;; i) Dired does not work with an unescaped newline in the directory name used in the + ;; header (bug=10469#28), and + ;; ii) "\" is always replaced with "\\" in the listing, so doing it in the header as + ;; well makes things consistent. + ;; But at present it is done only if "-b" is in ls-switches, because newlines in dirnames + ;; are uncommon, and people may have gotten used to seeing unescaped "\" in the headers. + ;; Note: adjust `dired-build-subdir-alist' if you change this. + (setq dir (replace-regexp-in-string "\\\\" "\\\\" dir nil t) + dir (replace-regexp-in-string "\n" "\\n" dir nil t))) + ;; If we used `--dired' and it worked, the lines are already indented. Else indent them. + (unless (save-excursion (goto-char opoint) (diredp-looking-at-p " ")) + (let ((indent-tabs-mode nil)) (indent-rigidly opoint (point) 2))) + ;; Insert text at the beginning to standardize things. + (let ((content-point opoint)) + (save-excursion + (goto-char opoint) + (when (and (or hdr wildcard) (not (and (looking-at "^ \\(.*\\):$") + (file-name-absolute-p (match-string 1))))) + ;; `dired-build-subdir-alist' will replace the name by its expansion, so it does not + ;; matter whether what we insert here is fully expanded, but it should be absolute. + (insert " " (directory-file-name (file-name-directory dir)) ":\n") + (setq content-point (point))) + (when wildcard + ;; Insert "wildcard" line where "total" line would be for a full dir. + (insert " wildcard " (file-name-nondirectory dir) "\n"))) + (dired-insert-set-properties content-point (point)))))) + + +;;; Image stuff. + +(defun diredp-image-dired-required-msg () + "Raise an error if `image-dired.el' is not loaded." + (unless (require 'image-dired nil t) (error "This command requires library `image-dired.el'"))) + +;; See `image-dired-create-thumb'. +;; Define this even if `image-dired.el' is not loaded. +;; Do NOT raise an error if not loaded, because this is used in `diredp-mouseover-help'. +;;;###autoload +(defun diredp-image-dired-create-thumb (file &optional arg) + "Create thumbnail image file for FILE (default: file on current line). +With a prefix arg, replace any existing thumbnail for FILE. +With a numeric prefix arg (not a cons), use it as the thumbnail size. +Return the name of the thumbnail image file, or nil if none." + (interactive (list (if (derived-mode-p 'dired-mode) + (dired-get-filename nil 'NO-ERROR) + ;; Make it work also for `diredp-list-files' listings. + (buffer-substring-no-properties (line-beginning-position) (line-end-position))) + current-prefix-arg)) + (and (fboundp 'image-dired-thumb-name) ; No-op (return nil) if `image-dired.el' not loaded. + (let ((thumb-name (image-dired-thumb-name file))) + (when arg (clear-image-cache)) + (when (or arg (not (file-exists-p thumb-name))) + (let ((image-dired-thumb-width (or (and arg (atom arg) arg) image-dired-thumb-width)) + (image-dired-thumb-height (or (and arg (atom arg) arg) image-dired-thumb-height))) + (unless (zerop (image-dired-create-thumb file thumb-name)) + (error "Thumbnail image file could not be created")))) + (and (file-exists-p thumb-name) thumb-name)))) + + +;; REPLACE ORIGINAL in `image-dired.el' (Emacs 22-23). +;; +;; 1. Raise an error if `image-dired.el' is not available. +;; 2. Repro it here so it picks up `Dired+' version of `dired-map-over-marks'. +;; +;;;###autoload +(defun image-dired-dired-insert-marked-thumbs () ; Bound to `C-t C-t' (Emacs 22-23) + "Insert thumbnails before file names of marked files in the Dired buffer." + (interactive (progn (diredp-image-dired-required-msg) ())) + (dired-map-over-marks + (let* ((image-pos (dired-move-to-filename)) + (image-file (dired-get-filename)) + (thumb-file (image-dired-get-thumbnail-image image-file)) + overlay) + ;; If image is not already added, then add it. + (unless (delq nil (mapcar (lambda (o) (overlay-get o 'put-image)) + ;; Can't use (overlays-at (point)), BUG? + (overlays-in (point) (1+ (point))))) + (put-image thumb-file image-pos) + (setq overlay (car (delq nil (mapcar (lambda (ov) (and (overlay-get ov 'put-image) ov)) + (overlays-in (point) (1+ (point))))))) + (overlay-put overlay 'image-file image-file) + (overlay-put overlay 'thumb-file thumb-file))) + nil) + (add-hook 'dired-after-readin-hook 'image-dired-dired-after-readin-hook nil t)) + + +;; REPLACE ORIGINAL in `image-dired.el' (Emacs 24+). +;; +;; 1. Raise an error if `image-dired.el' is not available. +;; 2. Repro it here so it picks up `Dired+' version of `dired-map-over-marks'. +;; +;;;###autoload +(defun image-dired-dired-toggle-marked-thumbs (&optional arg) ; Bound to `C-t C-t' (Emacs 24+) + "Toggle thumbnails in front of file names in Dired. +If no files are marked, insert or hide thumbnails on the current line. +With a numeric prefix arg N, ignore marked files and act on the next N +files (previous -N files, if N < 0)." + (interactive (progn (diredp-image-dired-required-msg) (list current-prefix-arg))) + (dired-map-over-marks + (let* ((image-pos (dired-move-to-filename)) + (image-file (diredp-get-image-filename nil 'NO-ERROR)) + thumb-file overlay) + (when image-file + (setq thumb-file (image-dired-get-thumbnail-image image-file)) + ;; If image is not already added, then add it. + (let* ((cur-ovs (overlays-in (point) (1+ (point)))) + (thumb-ov (car (diredp-remove-if-not (lambda (ov) (overlay-get ov 'thumb-file)) + cur-ovs)))) + (if thumb-ov + (delete-overlay thumb-ov) + (put-image thumb-file image-pos) + (setq overlay (car (delq nil (mapcar (lambda (ov) (and (overlay-get ov 'put-image) ov)) + (overlays-in (point) (1+ (point))))))) + (overlay-put overlay 'image-file image-file) + (overlay-put overlay 'thumb-file thumb-file))))) + arg + 'SHOW-PROGRESS) + (add-hook 'dired-after-readin-hook 'image-dired-dired-after-readin-hook nil t)) + +;; Corresponds to `image-dired-dired-comment-files'. +;;;###autoload +(defun diredp-image-dired-comment-file () + "Add comment to this image file." + (interactive (progn (diredp-image-dired-required-msg) ())) + (image-dired-write-comments (cons (dired-get-filename) (image-dired-read-comment)))) + +;; Corresponds to `image-dired-tag-files'. +;;;###autoload +(defun diredp-image-dired-tag-file () + "Tag this image file with an `image-dired' tag." + (interactive (progn (diredp-image-dired-required-msg) ())) + (image-dired-write-tags (cons (dired-get-filename) + (read-string "Tags to add (use `;' to separate): ")))) + +;; Corresponds to `image-dired-delete-tag'. +;;;###autoload +(defun diredp-image-dired-delete-tag () + "Remove an `image-dired' tag from this image file." + (interactive (progn (diredp-image-dired-required-msg) ())) + (image-dired-remove-tag (list (dired-get-filename)) (read-string "Tag to remove: "))) + +;; Corresponds to `image-dired-display-thumbs'. +;;;###autoload +(defun diredp-image-dired-display-thumb (&optional append) + "Pop to thumbnail of this image file, in `image-dired-thumbnail-buffer'. +If a thumbnail image does not yet exist for this file, create it. +With a prefix arg, append the thumbnail to the thumbnails buffer, +instead of clearing the buffer first." + (interactive (progn (diredp-image-dired-required-msg) (list current-prefix-arg))) + (let* ((dired-buf (current-buffer)) + (curr-file (dired-get-filename)) + (thumb-name (image-dired-thumb-name curr-file))) + (with-current-buffer (image-dired-create-thumbnail-buffer) + (let ((inhibit-read-only t)) + (if (not append) (erase-buffer) (goto-char (point-max))) + (if (and (not (file-exists-p thumb-name)) + (not (zerop (image-dired-create-thumb curr-file thumb-name)))) + (message "Cannot create thumbnail image for file `%s'" curr-file) + (image-dired-insert-thumbnail thumb-name curr-file dired-buf))) + (cond ((eq 'dynamic image-dired-line-up-method) (image-dired-line-up-dynamic)) + ((eq 'fixed image-dired-line-up-method) (image-dired-line-up)) + ((eq 'interactive image-dired-line-up-method) (image-dired-line-up-interactive)) + ((eq 'none image-dired-line-up-method) nil) + (t (image-dired-line-up-dynamic)))) + (pop-to-buffer image-dired-thumbnail-buffer))) + +;; Corresponds to `image-dired-copy-with-exif-file-name'. +;;;###autoload +(defun diredp-image-dired-copy-with-exif-name () + "Copy this image file to your main image directory. +Uses `image-dired-get-exif-file-name' to name the new file." + (interactive (progn (diredp-image-dired-required-msg) ())) + (let* ((curr-file (dired-get-filename)) + (new-name (format "%s/%s" (file-name-as-directory + (expand-file-name image-dired-main-image-directory)) + (image-dired-get-exif-file-name curr-file)))) + (message "Copying `%s' to `%s'..." curr-file new-name) + (copy-file curr-file new-name) + (message "Copying `%s' to `%s'...done" curr-file new-name))) + +;; Corresponds to `image-dired-dired-edit-comment-and-tags'. +;;;###autoload +(defun diredp-image-dired-edit-comment-and-tags () + "Edit comment and tags for this image file." + (interactive (progn (diredp-image-dired-required-msg) ())) + (setq image-dired-widget-list ()) + (let ((file (dired-get-filename))) + (if (fboundp 'pop-to-buffer-same-window) + (pop-to-buffer-same-window "*Image-Dired Edit Meta Data*") + (switch-to-buffer "*Image-Dired Edit Meta Data*")) + (kill-all-local-variables) + (make-local-variable 'widget-example-repeat) + (let ((inhibit-read-only t)) + (erase-buffer) + (remove-overlays) + (widget-insert + "\nEdit comment and tags for the image. Separate multiple tags +with a comma (`,'). Move forward among fields using `TAB' or `RET'. +Move backward using `S-TAB'. Click `Save' to save your edits or +`Cancel' to abandon them.\n\n") + (let* ((thumb-file (image-dired-thumb-name file)) + (img (create-image thumb-file)) + comment-widget tag-widget) + (insert-image img) + (widget-insert "\n\nComment: ") + (setq comment-widget (widget-create 'editable-field :size 60 :format "%v " + :value (or (image-dired-get-comment file) ""))) + (widget-insert "\nTags: ") + (setq tag-widget (widget-create 'editable-field :size 60 :format "%v " + :value (or (mapconcat #'identity (image-dired-list-tags file) ",") ""))) + ;; Save info in widgets to use when the user saves the form. + (setq image-dired-widget-list (append image-dired-widget-list + (list (list file comment-widget tag-widget)))) + (widget-insert "\n\n"))) + (widget-insert "\n") + (widget-create 'push-button :notify (lambda (&rest _ignore) + (image-dired-save-information-from-widgets) + (bury-buffer) + (message "Done")) + "Save") + (widget-insert " ") + (widget-create 'push-button :notify (lambda (&rest _ignore) + (bury-buffer) + (message "Operation canceled")) + "Cancel") + (widget-insert "\n") + (use-local-map widget-keymap) + (widget-setup) + (widget-forward 1))) ; Jump to the first widget. + +;;;###autoload +(defun diredp-do-display-images (&optional arg) + "Display the marked image files. +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any files are marked). + More than one `C-u' means use all files in the Dired buffer, as if + they were all marked." + (interactive (progn (unless (require 'image-file nil t) + (error "This command requires library `image-file.el'")) + (diredp-ensure-mode) + (list current-prefix-arg))) + (dired-map-over-marks-check #'diredp-display-image arg 'display\ image + (diredp-fewer-than-2-files-p arg))) + +(defun diredp-display-image () + "Display image file at point. Log an error using `dired-log'." + (let ((file (dired-get-filename 'LOCAL 'NO-ERROR)) + (failure nil)) + (save-excursion + (if (let ((inhibit-changing-match-data t)) + (and file (diredp-string-match-p (image-file-name-regexp) file))) + (condition-case err + (let ((find-file-run-dired nil)) (find-file-other-window file)) + (error (setq failure (error-message-string err)))) + (dired-log (format "Not an image file: `%s'" file)) + (setq failure t))) + (and failure ; Return nil for success. + (prog1 file ; Return file name for failure. + (unless (eq t failure) (dired-log "Cannot display image file `%s':\n%s\n" file failure) t))))) + +;;;###autoload +(defun diredp-image-show-this-file (&optional arg) + "Show the image file named on this line in another frame or window. +Option `diredp-image-show-this-file-use-frame-flag' which is used. + +With a prefix arg, shrink the image to fit a frame that many lines +high or a window at least that many lines high. +Otherwise, show the image full size. +Note: + * To show the image full size, you can also use `\\\\[dired-find-file]'. + * To show the image in another window, at whatever scale fits there, + you can use `\\[image-dired-dired-display-image]'." + (interactive (progn (diredp-image-dired-required-msg) (list current-prefix-arg))) + (image-dired-create-display-image-buffer) + (let ((fit-frame-inhibit-fitting-flag t) ; In `fit-frame.el'. + (img-file (diredp-get-image-filename))) + (if img-file + (with-current-buffer image-dired-display-image-buffer + (let* ((window-min-height (if arg + (prefix-numeric-value arg) + (ceiling (cdr (image-size (create-image img-file)))))) + (special-display-frame-alist (if diredp-image-show-this-file-use-frame-flag + (cons `(height . ,window-min-height) + special-display-frame-alist) + special-display-frame-alist)) + (special-display-buffer-names (if diredp-image-show-this-file-use-frame-flag + (cons image-dired-display-image-buffer + special-display-buffer-names) + special-display-buffer-names))) + (display-buffer image-dired-display-image-buffer) + (image-dired-display-image img-file (not arg)))) + (message "No image file here")))) ; An error is handled by `diredp-get-image-filename'. + +(defun diredp-report-file-result (file result failure echop) + (cond (failure + (when echop (message "Error for %s:\n%s\n" file failure) (sit-for 1)) + (dired-log "Error for %s:\n%s\n" file failure) + (dired-make-relative file)) ; Return file name for failure. + (t + (when echop (message "Result for %s:\n%s\n" file result) (sit-for 1)) + (dired-log "Result for %s:\n%s\n" file result) + nil))) ; Return nil for success. + +;;;###autoload +(defun diredp-do-emacs-command (command &optional arg) + "Invoke an Emacs COMMAND in each marked file. +Visit each marked file at its beginning, then invoke COMMAND. +You are prompted for the COMMAND. + +The result returned for each file is logged by `dired-log'. Use `?' +to see all such results and any error messages. If there are fewer +marked files than `diredp-do-report-echo-limit' then each result is +also echoed momentarily. + +A prefix argument behaves according to the ARG argument of +`dired-get-marked-files'. In particular, `C-u C-u' operates on all +files in the Dired buffer." + (interactive (progn (diredp-ensure-mode) + (list (diredp-read-command) current-prefix-arg))) + (save-selected-window + (diredp-map-over-marks-and-report + #'diredp-invoke-emacs-command arg 'invoke\ emacs\ command (diredp-fewer-than-2-files-p arg) + command (diredp-fewer-than-echo-limit-files-p arg)))) + +(defun diredp-invoke-emacs-command (command &optional echop) + "Visit file of this line at its beginning, then invoke COMMAND. +Log the result returned or any error. +Non-nil optional arg ECHOP means also echo the result." + (let* ((file (dired-get-filename)) + (failure (not (file-exists-p file))) + result) + (unless failure + (condition-case err + (with-current-buffer (find-file-noselect file) + (save-excursion + (goto-char (point-min)) + (setq result (call-interactively command)))) + (error (setq failure err)))) + (diredp-report-file-result file result failure echop))) + +(defun diredp-read-command (&optional prompt default) + "Read the name of a command and return a symbol with that name. +\(A command is anything that satisfies predicate `commandp'.) +Prompt with PROMPT, which defaults to \"Command: \". +By default, return the command named DEFAULT (or, with Emacs 23+, its +first element if DEFAULT is a list). (If DEFAULT does not name a +command then it is ignored.)" + (setq prompt (or prompt "Command: ")) + (let ((name (completing-read prompt obarray #'commandp t nil + 'extended-command-history default))) + (while (string= "" name) + (setq name (completing-read prompt obarray #'commandp t nil + 'extended-command-history default))) + (intern name))) + +(when (fboundp 'diredp-read-expression) ; Emacs 22+ + + (defun diredp-do-lisp-sexp (sexp &optional arg) + "Evaluate an Emacs-Lisp SEXP in each marked file. +Visit each marked file at its beginning, then evaluate SEXP. +You are prompted for the SEXP. + +The result returned for each file is logged by `dired-log'. Use `?' +to see all such results and any error messages. If there are fewer +marked files than `diredp-do-report-echo-limit' then each result is +also echoed momentarily. + +A prefix argument behaves according to the ARG argument of +`dired-get-marked-files'. In particular, `C-u C-u' operates on all +files in the Dired buffer." + (interactive (progn (diredp-ensure-mode) + (list (diredp-read-expression "Sexp: ") current-prefix-arg))) + (save-selected-window + (diredp-map-over-marks-and-report + #'diredp-eval-lisp-sexp arg 'eval\ elisp\ sexp (diredp-fewer-than-2-files-p arg) + sexp (diredp-fewer-than-echo-limit-files-p arg)))) + + (defun diredp-eval-lisp-sexp (sexp &optional echop) + "Visit file of this line at its beginning, then evaluate SEXP. +Log the result returned or any error. +Non-nil optional arg ECHOP means also echo the result." + (let* ((file (dired-get-filename)) + (failure (not (file-exists-p file))) + result) + (unless failure + (condition-case err + (with-current-buffer (find-file-noselect file) + (save-excursion + (goto-char (point-min)) + (setq result (eval-expression sexp)))) + (error (setq failure err)))) + (diredp-report-file-result file result failure echop))) + + ) + +;;; Face Definitions + +(defface diredp-autofile-name + '((((background dark)) (:background "#111313F03181")) ; Very dark blue + (t (:background "#EEECEC0FCE7E"))) ; Very pale goldenrod + "*Face used in Dired for names of files that are autofile bookmarks." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-autofile-name 'diredp-autofile-name) + +(defface diredp-compressed-file-name + '((((background dark)) (:foreground "Blue")) + (t (:foreground "Brown"))) + "*Face used for compressed file names." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-compressed-file-name 'diredp-compressed-file-name) + +(defface diredp-compressed-file-suffix + '((((background dark)) (:foreground "Blue")) + (t (:foreground "Yellow"))) + "*Face used for compressed file suffixes in Dired buffers. +This means the `.' plus the file extension. Example: `.zip'." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-compressed-file-suffix 'diredp-compressed-file-suffix) + +(defface diredp-date-time + '((((background dark)) (:foreground "#74749A9AF7F7")) ; ~ med blue + (t (:foreground "DarkGoldenrod4"))) + "*Face used for date and time in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-date-time 'diredp-date-time) + +(defface diredp-deletion + '((t (:foreground "Yellow" :background "Red"))) + "*Face used for deletion flags (D) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-deletion 'diredp-deletion) + +(defface diredp-deletion-file-name + '((t (:foreground "Red"))) + "*Face used for names of deleted files in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-deletion-file-name 'diredp-deletion-file-name) + +(defface diredp-dir-heading + '((((background dark)) (:foreground "Yellow" :background "#00003F3F3434")) ; ~ dark green + (t (:foreground "Blue" :background "Pink"))) + "*Face used for directory headings in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-dir-heading 'diredp-dir-heading) + +(defface diredp-dir-name + '((((background dark)) + (:foreground "#7474FFFFFFFF" :background "#2C2C2C2C2C2C")) ; ~ cyan, dark gray + (t (:foreground "DarkRed" :background "LightGray"))) + "*Face used for directory names." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-dir-name 'diredp-dir-name) + +(defface diredp-dir-priv + '((((background dark)) + (:foreground "#7474FFFFFFFF" :background "#2C2C2C2C2C2C")) ; ~ cyan, dark gray + (t (:foreground "DarkRed" :background "LightGray"))) + "*Face used for directory privilege indicator (d) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-dir-priv 'diredp-dir-priv) + +(defface diredp-exec-priv + '((((background dark)) (:background "#4F4F3B3B2121")) ; ~ dark brown + (t (:background "LightSteelBlue"))) + "*Face used for execute privilege indicator (x) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-exec-priv 'diredp-exec-priv) + +;; For this to show up, you need `F' among the options in `dired-listing-switches'. +;; For example, I use "-alF" for `dired-listing-switches'. +(defface diredp-executable-tag + '((t (:foreground "Red"))) + "*Face used for executable tag (*) on file names in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-executable-tag 'diredp-executable-tag) + +(defface diredp-file-name + '((((background dark)) (:foreground "Yellow")) + (t (:foreground "Blue"))) + "*Face used for file names (without suffixes) in Dired buffers. +This means the base name. It does not include the `.'." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-file-name 'diredp-file-name) + +(defface diredp-file-suffix + '((((background dark)) (:foreground "#7474FFFF7474")) ; ~ light green + (t (:foreground "DarkMagenta"))) + "*Face used for file suffixes in Dired buffers. +This means the `.' plus the file extension. Example: `.elc'." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-file-suffix 'diredp-file-suffix) + +(defface diredp-flag-mark + '((((background dark)) (:foreground "Blue" :background "#7575D4D41D1D")) ; ~ olive green + (t (:foreground "Yellow" :background "Blueviolet"))) + "*Face used for flags and marks (except D) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-flag-mark 'diredp-flag-mark) + +(defface diredp-flag-mark-line + '((((background dark)) (:background "#787831311414")) ; ~ dark red brown + (t (:background "Skyblue"))) + "*Face used for flagged and marked lines in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-flag-mark-line 'diredp-flag-mark-line) + +(defface diredp-ignored-file-name + '((((background dark)) (:foreground "#C29D6F156F15")) ; ~ salmon + (t (:foreground "#00006DE06DE0"))) ; ~ dark cyan + "*Face used for files whose names are omitted based on the extension. +See also face `diredp-omit-file-name'." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-ignored-file-name 'diredp-ignored-file-name) + +(defface diredp-link-priv + '((((background dark)) (:foreground "#00007373FFFF")) ; ~ blue + (t (:foreground "DarkOrange"))) + "*Face used for link privilege indicator (l) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-link-priv 'diredp-link-priv) + +(when (> emacs-major-version 21) + (defface diredp-mode-line-marked + '((t (:foreground "DarkViolet"))) + "*Face for marked number in mode-line `mode-name' for Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) + + (defface diredp-mode-line-flagged + '((t (:foreground "Red"))) + "*Face for flagged number in mode-line `mode-name' for Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces)) + +(defface diredp-no-priv + '((((background dark)) (:background "#2C2C2C2C2C2C")) ; ~ dark gray + (t (:background "LightGray"))) + "*Face used for no privilege indicator (-) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-no-priv 'diredp-no-priv) + +(defface diredp-number + '((((background dark)) (:foreground "#FFFFFFFF7474")) ; ~ light yellow + (t (:foreground "DarkBlue"))) + "*Face used for numerical fields in Dired buffers. +In particular, inode number, number of hard links, and file size." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-number 'diredp-number) + +(defface diredp-omit-file-name + (if (assq :inherit custom-face-attributes) ; Emacs 22+ + '((((background dark)) (:inherit diredp-ignored-file-name :strike-through "#555555555555")) ; ~ dark gray + (t (:inherit diredp-ignored-file-name :strike-through "#AAAAAAAAAAAA"))) ; ~ light gray + '((((background dark)) (:foreground "#C29D6F156F15")) ; ~ salmon + (t (:foreground "#00006DE06DE0")))) ; ~ dark cyan + "*Face used for files whose names will be omitted in `dired-omit-mode'. +This means file names that match regexp `diredp-omit-files-regexp'. +\(File names matching `dired-omit-extensions' are highlighted with face +`diredp-ignored-file-name' instead.)" + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-omit-file-name 'diredp-omit-file-name) + +(defface diredp-other-priv + '((((background dark)) (:background "#111117175555")) ; ~ dark blue + (t (:background "PaleGoldenrod"))) + "*Face used for l,s,S,t,T privilege indicators in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-other-priv 'diredp-other-priv) + +(defface diredp-rare-priv + '((((background dark)) (:foreground "Green" :background "#FFFF00008080")) ; ~ hot pink + (t (:foreground "Magenta" :background "SpringGreen"))) + "*Face used for rare privilege indicators (b,c,s,m,p,S) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-rare-priv 'diredp-rare-priv) + +(defface diredp-read-priv + '((((background dark)) (:background "#999932325555")) ; ~ burgundy / dark magenta + (t (:background "MediumAquamarine"))) + "*Face used for read privilege indicator (w) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-read-priv 'diredp-read-priv) + +(defface diredp-symlink + '((((background dark)) (:foreground "#00007373FFFF")) ; ~ blue + (t (:foreground "DarkOrange"))) + "*Face used for symbolic links in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-symlink 'diredp-symlink) + +(defface diredp-tagged-autofile-name + '((((background dark)) (:background "#328C0411328C")) ; Very dark magenta + (t (:background "#CD73FBEECD73"))) ; Very pale green + "*Face used in Dired for names of files that are autofile bookmarks." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-tagged-autofile-name 'diredp-tagged-autofile-name) + +(defface diredp-write-priv + '((((background dark)) (:background "#25258F8F2929")) ; ~ dark green + (t (:background "Orchid"))) + "*Face used for write privilege indicator (w) in Dired buffers." + :group 'Dired-Plus :group 'font-lock-highlighting-faces) +(defvar diredp-write-priv 'diredp-write-priv) + +;; Fix Emacs 20 recognition of fields up through file name when size is expressed using `k' etc. +(when (and (< emacs-major-version 21) (not (boundp 'diredp-loaded-p)) + dired-move-to-filename-regexp ; These last two checks are just in case. + (eq (aref dired-move-to-filename-regexp 7) ?\ )) + (setq dired-move-to-filename-regexp (concat "[0-9][BkKMGTPEZY]?" + (substring dired-move-to-filename-regexp 7)))) + +;;; Define second level of fontifying. +(defvar diredp-font-lock-keywords-1 + (list + '("^ \\(.+:\\)$" 1 diredp-dir-heading) ; Directory headers + '("^ wildcard.*$" 0 'default) ; Override others, e.g. `l' for `diredp-other-priv'. + '("^ (No match).*$" 0 'default) ; Override others, e.g. `t' for `diredp-other-priv'. + '("[^ .]\\(\\.[^. /]+\\)$" 1 diredp-file-suffix) ; Suffix, including `.'. + '("\\([^ ]+\\) -> .+$" 1 diredp-symlink) ; Symbolic links + + ;; 1) Date/time and 2) filename w/o suffix. + ;; This is a bear, and it is fragile - Emacs can change `dired-move-to-filename-regexp'. + (if (or (not (fboundp 'version<)) (version< emacs-version "23.2")) + (list dired-move-to-filename-regexp + (list 1 'diredp-date-time t t) ; Date/time + (list (concat "\\(.+\\)\\(" (concat (funcall #'regexp-opt diredp-compressed-extensions) + "\\)[*]?$")) ; Compressed-file name + nil nil (list 0 diredp-compressed-file-name 'keep t))) + `(,dired-move-to-filename-regexp + (7 diredp-date-time t t) ; Date/time, locale (western or eastern) + (2 diredp-date-time t t) ; Date/time, ISO + (,(concat "\\(.+\\)\\(" (concat (funcall #'regexp-opt diredp-compressed-extensions) + "\\)[*]?$")) + nil nil (0 diredp-compressed-file-name keep t)))) ; Compressed-file suffix + (if (or (not (fboundp 'version<)) (version< emacs-version "23.2")) + (list dired-move-to-filename-regexp + (list 1 'diredp-date-time t t) ; Date/time + (list "\\(.+\\)$" nil nil (list 0 diredp-file-name 'keep t))) ; Filename + `(,dired-move-to-filename-regexp + (7 diredp-date-time t t) ; Date/time, locale (western or eastern) + (2 diredp-date-time t t) ; Date/time, ISO + ("\\(.+\\)$" nil nil (0 diredp-file-name keep t)))) ; Filename (not a compressed file) + + ;; Files to ignore. + ;; Use face `diredp-ignored-file-name' for omission by file-name extension. + ;; Use face `diredp-omit-file-name' for omission by entire file name. + (let* ((omit-exts (or (and (boundp 'dired-omit-extensions) dired-omit-extensions) + completion-ignored-extensions)) + (omit-exts (and omit-exts (mapconcat #'regexp-quote omit-exts "\\|"))) + (compr-exts (and diredp-ignore-compressed-flag + (concat "\\|" (mapconcat #'regexp-quote diredp-compressed-extensions "\\|"))))) + (list (concat "^ \\(.*\\(" omit-exts compr-exts "\\)[*]?\\)$") ; [*]? allows for executable flag (*). + 1 diredp-ignored-file-name t)) + `(,(concat "^.*" dired-move-to-filename-regexp + "\\(" diredp-omit-files-regexp "\\).*[*]?$") ; [*]? allows for executable flag (*). + (0 diredp-omit-file-name t)) + + ;; Compressed-file (suffix) + (list (concat "\\(" (funcall #'regexp-opt diredp-compressed-extensions) "\\)[*]?$") + 1 diredp-compressed-file-suffix t) + '("\\([*]\\)$" 1 diredp-executable-tag t) ; Executable (*) + + ;; Inode, hard-links, & file size (. and , are for the decimal point, depending on locale) + ;; See comment for `directory-listing-before-filename-regexp' in `files.el' or `files+.el'. + '("\\(\\([0-9]+\\([.,][0-9]+\\)?\\)[BkKMGTPEZY]?[ /]?\\)" 1 diredp-number) + + ;; Directory names - exclude d:/..., Windows drive letter in a dir heading. + (list (concat dired-re-maybe-mark dired-re-inode-size "\\(d\\)[^:]") + '(1 diredp-dir-priv t) '(".+" (dired-move-to-filename) nil (0 diredp-dir-name t))) + + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]........\\(x\\)") ; o x + '(1 diredp-exec-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]........\\([lsStT]\\)") ; o misc + '(1 diredp-other-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].......\\(w\\).") ; o w + '(1 diredp-write-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]......\\(r\\)..") ; o r + '(1 diredp-read-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].....\\(x\\)...") ; g x + '(1 diredp-exec-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].....\\([lsStT]\\)...") ; g misc + '(1 diredp-other-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]....\\(w\\)....") ; g w + '(1 diredp-write-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]...\\(r\\).....") ; g r + '(1 diredp-read-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]..\\(x\\)...") ; u x + '(1 diredp-exec-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]..\\([lsStT]\\)...") ; u misc + '(1 diredp-other-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].\\(w\\)....") ; u w + '(1 diredp-write-priv)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]\\(r\\).....") ; u r + '(1 diredp-read-priv)) + + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]........\\([-rwxlsStT]\\)") ; o - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].......\\([-rwxlsStT]\\).") ; g - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]......\\([-rwxlsStT]\\)..") ; u - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].....\\([-rwxlsStT]\\)...") ; o - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]....\\([-rwxlsStT]\\)....") ; g - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]...\\([-rwxlsStT]\\).....") ; u - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]..\\([-rwxlsStT]\\)......") ; o - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl].\\([-rwxlsStT]\\).......") ; g - + '(1 diredp-no-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "[-dl]\\([-rwxlsStT]\\)........") ; u - + '(1 diredp-no-priv keep)) + + (list (concat dired-re-maybe-mark dired-re-inode-size "\\([bcsmpS]\\)") ; (rare) + '(1 diredp-rare-priv keep)) + (list (concat dired-re-maybe-mark dired-re-inode-size "\\(l\\)[-rwxlsStT]") ; l + '(1 diredp-rare-priv keep)) + + (list (concat "^\\([^\n " (char-to-string dired-del-marker) "].*$\\)") + 1 diredp-flag-mark-line t) ; Flag/mark lines + (list (concat "^\\([^\n " (char-to-string dired-del-marker) "]\\)") ; Flags, marks (except D) + 1 diredp-flag-mark t) + + (list (concat "^\\([" (char-to-string dired-del-marker) "].*$\\)") ; Deletion-flagged lines + 1 diredp-deletion-file-name t) + (list (concat "^\\([" (char-to-string dired-del-marker) "]\\)") ; Deletion flags (D) + 1 diredp-deletion t) + + ) "2nd level of Dired highlighting. See `font-lock-maximum-decoration'.") + + +(defun diredp--set-up-font-locking () + "Add this to `dired-mode-hook' to provide for second-level fontifying." + (set (make-local-variable 'font-lock-defaults) + ;; Two levels. Use 3-element list, since it is standard to have one more + ;; than the number of levels. This is necessary for it to work with + ;; `font(-lock)-menus.el'. + '((dired-font-lock-keywords + dired-font-lock-keywords + diredp-font-lock-keywords-1) + t nil nil beginning-of-line)) + ;; Refresh `font-lock-keywords' from `font-lock-defaults' + (when (fboundp 'font-lock-refresh-defaults) (font-lock-refresh-defaults))) + +;;; Provide for the second level of fontifying. +(add-hook 'dired-mode-hook 'diredp--set-up-font-locking) + +;; Ensure that Dired buffers are refontified when you use `g' or otherwise read in the file list. +(defun diredp-refontify-buffer () + "Turn `font-lock-mode' off, then on." + (setq font-lock-mode nil) + (font-lock-mode)) +(add-hook 'dired-after-readin-hook 'diredp-refontify-buffer) + +;;; Function Definitions + +;;; $$$$$$$$ +;;; (defun diredp-dired-files (arg &optional switches) ; Not bound +;;; "Like `dired', but non-positive prefix arg prompts for files to list. +;;; This is like `dired' unless you use a non-positive prefix arg. +;;; In that case, you are prompted for names of files and directories to +;;; list, and then you are prompted for the name of the Dired buffer that +;;; lists them. Use `C-g' when you are done entering file names to list. + +;;; In all cases, when inputting a file or directory name you can use +;;; shell wildcards. + +;;; If you use Icicles, then in Icicle mode the following keys are bound +;;; in the minibuffer during completion (`*' means the key requires +;;; library `Bookmark+'): + +;;; M-| - Open Dired on the file names matching your input +;;; C-c + - Create a new directory +;;; *C-x a + - Add tags to the current-candidate file +;;; *C-x a - - Remove tags from the current-candidate file +;;; *C-x m - Access file bookmarks (not just autofiles)" +;;; (interactive (diredp-dired-files-interactive-spec "")) +;;; (when (consp arg) +;;; (let ((buf (dired-find-buffer-nocreate (car arg)))) ; Respect file list. +;;; (when buf (kill-buffer buf)))) +;;; (if (fboundp 'pop-to-buffer-same-window) +;;; (pop-to-buffer-same-window (dired-noselect arg switches)) +;;; (switch-to-buffer (dired-noselect arg switches)))) + +;;; (defun diredp-dired-files-other-window (arg &optional switches) ; Not bound +;;; "Same as `diredp-dired-files' except uses another window." +;;; (interactive (diredp-dired-files-interactive-spec "in other window ")) +;;; (when (consp arg) +;;; (let ((buf (dired-find-buffer-nocreate (car arg)))) ; Respect file list. +;;; (when buf (kill-buffer buf)))) +;;; (dired-other-window arg switches)) + +;;;###autoload +(defun diredp-dired-for-files (arg &optional switches) ; Bound to `C-x D F' + "Dired file names that you enter, in a Dired buffer that you name. +You are prompted for the name of the Dired buffer to use. +You are then prompted for names of files and directories to list, + which can be located anywhere. +Use `C-g' when you are done. + +With a prefix arg you are first prompted for the `ls' switches to use. + +See also `dired' (including the advice)." + (interactive (let ((current-prefix-arg (if current-prefix-arg 0 -1))) + (dired-read-dir-and-switches "" 'READ-EXTRA-FILES-P))) + (dired arg switches)) + +;;;###autoload +(defun diredp-dired-for-files-other-window (arg &optional switches) ; Bound to `C-x 4 D F' + "Same as `diredp-dired-for-files', except uses another window." + (interactive (let ((current-prefix-arg (if current-prefix-arg 0 -1))) + (dired-read-dir-and-switches "in other window " 'READ-EXTRA-FILES-P))) + (dired-other-window arg switches)) + +;;;###autoload +(defun diredp-dired-recent-dirs (buffer &optional arg) ; Bound to `C-x D R' + "Open Dired in BUFFER, showing recently used directories. +You are prompted for BUFFER. + +No prefix arg or a plain prefix arg (`C-u', `C-u C-u', etc.) means +list all of the recently used directories. + +With a prefix arg: +* If 0, `-', or plain (`C-u') then you are prompted for the `ls' + switches to use. +* If not plain (`C-u') then: + * If >= 0 then the directories to include are read, one by one. + * If < 0 then the directories to exclude are read, one by one. + +When entering directories to include or exclude, use `C-g' to end." + (interactive (list (completing-read "Dired buffer name: " dired-buffers) current-prefix-arg)) + (unless (require 'recentf nil t) (error "This command requires library `recentf.el'")) + (let ((switches (and (or (zerop (prefix-numeric-value arg)) (consp arg)) + (read-string "Dired listing switches: " dired-listing-switches)))) + (dired (cons (generate-new-buffer-name buffer) (diredp-recent-dirs arg)) switches))) + +;;;###autoload +(defun diredp-dired-recent-dirs-other-window (buffer &optional arg) ; Bound to `C-x 4 D R' + "Same as `diredp-dired-recent-dirs', but use other window." + (interactive (list (completing-read "Dired buffer name: " dired-buffers) current-prefix-arg)) + (unless (require 'recentf nil t) (error "This command requires library `recentf.el'")) + (let ((switches (and (or (zerop (prefix-numeric-value arg)) (consp arg) (eq '- arg)) + (read-string "Dired listing switches: " dired-listing-switches)))) + (dired-other-window (cons (generate-new-buffer-name buffer) (diredp-recent-dirs arg)) switches))) + +(defun diredp-recent-dirs (arg) + "Return a list of recently used directories. +ARG is as for `diredp-dired-recent-dirs'." + (let ((recent-dirs (diredp-remove-if #'diredp-root-directory-p + (diredp-delete-dups + (mapcar (lambda (f/d) + (if (file-directory-p f/d) f/d (file-name-directory f/d))) + recentf-list))))) + (if (and arg (atom arg)) + (diredp-read-include/exclude 'Dir recent-dirs (not (natnump (prefix-numeric-value arg)))) + recent-dirs))) + +(defun diredp-read-include/exclude (thing things &optional exclude) + "Read which THINGs to include (or to EXCLUDE, if non-nil) from list THINGS. +The things are read one by one. `C-g' stops reading. + +THING is a string or symbol naming the type of thing to read, e.g., +`File' or `Directory'. It is used only in the prompt, which is THING +followed by \" to exclude\" or \" to include\" and a reminder about `C-g'. + +A new list is returned - list THINGS is not modified." + (let* ((thgs (if exclude (copy-sequence things) ())) + (prompt (format "%s to %s (C-g when done): " thing (if exclude 'EXCLUDE 'INCLUDE))) + (completion-ignore-case (or (and (boundp 'read-file-name-completion-ignore-case) + (memq thing '(Dir Directory File "Dir" "Directory" "File")) ; Hack + read-file-name-completion-ignore-case) + completion-ignore-case)) + thing) + (while (condition-case nil + (setq thing (completing-read prompt (mapcar #'list things) nil t)) + (quit nil)) + (if exclude (delete thing thgs) + (push thing thgs))) + thgs)) + +;;; $$$$$$$$ +;;; (defun diredp-dired-files-interactive-spec (str) +;;; "`interactive' spec for `diredp-dired-files' commands. +;;; STR is a string appended to the prompt. +;;; With non-negative prefix arg, read switches. +;;; With non-positive prefix arg, read files and dirs to list and then the +;;; Dired buffer name. User uses `C-g' when done reading files and dirs. + +;;; If you use Icicles, then in Icicle mode the following keys are bound +;;; in the minibuffer during completion (`*' means the key requires +;;; library `Bookmark+'): + +;;; M-| - Open Dired on the file names matching your input +;;; C-c + - Create a new directory +;;; *C-x a + - Add tags to the current-candidate file +;;; *C-x a - - Remove tags from the current-candidate file +;;; *C-x m - Access file bookmarks (not just autofiles)" +;;; (list +;;; (unwind-protect +;;; (let ((icicle-sort-comparer (or (and (boundp 'icicle-file-sort) ;; If not reading files +;;; icicle-file-sort) ;; then dirs first. +;;; (and (> (prefix-numeric-value current-prefix-arg) 0) +;;; 'icicle-dirs-first-p) +;;; icicle-sort-comparer)) +;;; (icicle-all-candidates-list-alt-action-fn ; M-|' +;;; (lambda (files) +;;; (let ((enable-recursive-minibuffers t)) +;;; (dired-other-window (cons (read-string "Dired buffer name: ") files)))))) +;;; (when (fboundp 'icicle-bind-file-candidate-keys) (icicle-bind-file-candidate-keys)) +;;; (if (> (prefix-numeric-value current-prefix-arg) 0) +;;; ;; If a dialog box is about to be used, call `read-directory-name' so the dialog +;;; ;; code knows we want directories. Some dialog boxes can only select directories +;;; ;; or files when popped up, not both. +;;; (if (and (fboundp 'read-directory-name) (next-read-file-uses-dialog-p)) +;;; (read-directory-name (format "Dired %s(directory): " str) nil +;;; default-directory nil) +;;; (read-file-name (format "Dired %s(directory): " str) nil default-directory nil)) +;;; (let ((insert-default-directory nil) +;;; (files ()) +;;; file) +;;; (while (condition-case nil ; Use lax completion, to allow wildcards. +;;; (setq file (read-file-name "File or dir (C-g when done): ")) +;;; (quit nil)) +;;; (push file files)) +;;; (cons (read-string "Dired buffer name: " nil nil default-directory) files)))) +;;; (when (fboundp 'icicle-unbind-file-candidate-keys) +;;; (icicle-unbind-file-candidate-keys))) +;;; (and current-prefix-arg (natnump (prefix-numeric-value current-prefix-arg)) +;;; (read-string "Dired listing switches: " dired-listing-switches)))) + +;;;###autoload +(defun diredp-dired-union (dired-name dirbufs &optional switches extra) ; Bound to `C-x D U' + "Create a Dired buffer that is the union of some existing Dired buffers. +With a non-negative prefix arg, you are prompted for `ls' switches. +With a non-positive prefix arg, you are prompted for file and dir +names to add to the listing - see below. + +You are prompted for the name of the Dired union buffer. Completion +against names of existing Dired buffers is available, but you can +enter any other name to create a new Dired buffer of that name. + +If the union buffer name you choose names an existing Dired buffer, +then what happens depends on whether that buffer is an ordinary Dired +directory listing or a list of arbitrary file names. That is, it +depends on whether `dired-directory' is a directory name or a cons of +a Dired buffer name plus file names. + +* If the buffer is an ordinary Dired listing, then it is converted to + an explicit list of absolute file names, just as if these had been + chosen individually. The existing buffer and window are replaced by + new ones that show the explicit listing. (This replacement is + necessary because the list of files contained in an ordinary Dired + listing cannot be modified.) + +* If the buffer lists arbitrary file names explicitly, then it is + updated to include also the files from any Dired buffers and any + additional files that you specify. + +If the union buffer name you choose does not name an existing Dired +buffer, then its `default-directory' is the same as the +`default-directory' before invoking the command. + +If you use a non-positive prefix arg, then you can next choose +additional file and directory names to add to the listing. Use `C-g' +when done choosing them. + +Any directory names you choose this way are included as single entries +in the listing - the directory contents are not included (these +directories are not unioned). To instead include the contents of a +directory chosen this way, use a glob pattern: `/*' after the +directory name. + +You are then prompted for the Dired buffers to union. Use `C-g' when +done choosing them. These Dired listings to union are included in the +order that you chose them, and each entry is listed only once in the +new Dired buffer. + +The new Dired listing respects the markings, subdirectory insertions, +and hidden subdirectories of the selected Dired listings. However, in +case of conflict between marked or unmarked status for the same entry, +the entry is marked. Similarly, in case of conflict over an included +subdirectory between it being hidden or shown, it is hidden, but its +contained files are also listed. + +See also command `diredp-add-to-dired-buffer'. + +From Lisp: + DIRED-NAME is the name of the resulting Dired union buffer. + DIRBUFS is a list of the names of Dired buffers to union. + SWITCHES is a string of `ls' switches. + EXTRA is a list of files & directories to be included in the listing." + (interactive (diredp-dired-union-interactive-spec "UNION " + nil + (and current-prefix-arg + (<= (prefix-numeric-value current-prefix-arg) 0)))) + (diredp-dired-union-1 dired-name dirbufs switches extra)) + +;;;###autoload +(defun diredp-dired-union-other-window (dired-name dirbufs &optional switches extra) ; Bound to `C-x 4 D U' + "Same as `diredp-dired-union', except use other window." + (interactive (diredp-dired-union-interactive-spec "UNION " + nil + (and current-prefix-arg + (<= (prefix-numeric-value current-prefix-arg) 0)))) + (diredp-dired-union-1 dired-name dirbufs switches extra 'OTHERWIN)) + +;;;###autoload +(defun diredp-add-to-dired-buffer (dired-name to-add &optional switches) ; Bound to `C-x D A' + "Add individual file and directory names to a Dired buffer. +You are prompted for the buffer name. +With a prefix arg, you are also prompted for the `ls' switches. + +The buffer must either not exist yet or must list arbitrary file and +directory names. That is, it cannot be an ordinary Dired directory +listing - those cannot be modified. + +Any directory names you choose this way are included as single entries +in the listing - the directory contents are not included (these +directories are not unioned). To instead include the contents of a +directory chosen this way, use a glob pattern: `/*' after the +directory name. + +See also command `diredp-dired-union'. + +From Lisp: + DIRED-NAME is the name of the Dired buffer to modify. + TO-ADD is the list of files and dirs to add to it. + SWITCHES is the string of `ls' switches." + ;; Bind `current-prefix-arg' to force reading file/dir names. + ;; Read `ls' switches too, if user used prefix arg. + (interactive + (let* ((current-prefix-arg (if current-prefix-arg 0 -1)) + (all (diredp-dired-union-interactive-spec "add files/dirs " + 'NO-DIRED-BUFS + 'READ-EXTRA-FILES-P))) + (list (nth 0 all) (nth 3 all) (nth 2 all)))) + (diredp-dired-union-1 dired-name () switches to-add)) + +;;;###autoload +(defun diredp-add-to-dired-buffer-other-window (dired-name to-add &optional switches) ; Bound to `C-x 4 D A' + "Same as `diredp-add-to-dired-buffer', except use other window." + ;; Bind `current-prefix-arg' to force reading file/dir names. + ;; Read `ls' switches too, if user used prefix arg. + (interactive + (let* ((current-prefix-arg (if current-prefix-arg 0 -1)) + (all (diredp-dired-union-interactive-spec "add files/dirs " + 'NO-DIRED-BUFS + 'READ-EXTRA-FILES-P))) + (list (nth 0 all) (nth 3 all) (nth 2 all)))) + (diredp-dired-union-1 dired-name () switches to-add 'OTHERWIN)) + +;;;###autoload +(defun diredp-add-to-this-dired-buffer (dired-name to-add &optional switches) ; Not bound by default + "Same as `diredp-add-to-dired-buffer' for this Dired buffer." + ;; Bind `current-prefix-arg' to force reading file/dir names. + ;; Read `ls' switches too, if user used prefix arg. + (interactive + (progn (unless (derived-mode-p 'dired-mode) (error "Not in a Dired buffer")) + (let* ((current-prefix-arg (if current-prefix-arg 0 -1)) + (all (diredp-dired-union-interactive-spec "add files/dirs here " + 'NO-DIRED-BUFS + 'READ-EXTRA-FILES-P + (buffer-name)))) + (list (nth 0 all) (nth 3 all) (nth 2 all))))) + (diredp-dired-union-1 dired-name () switches to-add)) + +;; $$$$$ Maybe I should set `dired-sort-inhibit' to t for now (?), +;; since there is an Emacs bug (at least on Windows) that prevents +;; sorting from working for a Dired buffer with an explicit file list. +(defun diredp-dired-union-1 (dired-name dirbufs switches extra &optional otherwin) + "Helper for `diredp-dired-union' and `diredp-add-to-dired-buffer'. +Non-nil optional OTHERWIN means use other window for the Dired buffer. +See `diredp-dired-union' for the other argument descriptions." + (let ((dbuf (get-buffer dired-name)) + (files extra) + (marked ()) + (subdirs ()) + (hidden-dirs ()) + hid-here files-here) + (dolist (buf (reverse dirbufs)) + (with-current-buffer buf + (unwind-protect + (progn (setq hid-here (save-excursion (dired-remember-hidden)) + files-here (if (consp dired-directory) + (reverse (cdr dired-directory)) ; Reverse bc will push. + ())) + (unless files-here + (save-excursion ; This bit is more or less from `dired-toggle-marks'. + (goto-char (point-min)) + (while (not (eobp)) + (or (diredp-looking-at-p dired-re-dot) + (push (dired-get-filename nil 'NO-ERROR-P) files-here)) + (forward-line 1))) + (setq files-here (delq nil files-here))) + (dolist (hid-here hid-here) (push hid-here hidden-dirs)) + (dolist (sub (cdr (reverse dired-subdir-alist))) + (push (list (car sub)) subdirs)) + (dolist (mkd (dired-remember-marks (point-min) (point-max))) ; This unhides. + (push (car mkd) marked)) + (dolist (file files-here) + (when (or (not (file-name-absolute-p file)) (not (member file files))) + (push file files)))) + (save-excursion ; Hide subdirs that were hidden. + (dolist (dir hid-here) (when (dired-goto-subdir dir) (dired-hide-subdir 1))))))) + ;; For an existing Dired buffer having this name whose `dired-directory' is a cons: + ;; 1. Include the files and dirs already listed there. + ;; 2. Kill the current buffer and delete its window. A new buffer of the same name is created and shown. + (when dbuf + (with-current-buffer dbuf + (when (consp dired-directory) (setq files (diredp-set-union (cdr dired-directory) files))) + (let ((win (get-buffer-window dbuf 0))) (when win (delete-window win))) + (kill-buffer dbuf))) + (setq dbuf (dired-other-window (cons dired-name files) switches)) + (with-current-buffer dbuf + (let ((inhibit-read-only t)) + (dired-insert-old-subdirs subdirs) + (dired-mark-remembered ; Don't really need `expand-file-name' - already abs. + (mapcar (lambda (mf) (cons (expand-file-name mf dired-directory) 42)) marked)) + (save-excursion + (dolist (hdir hidden-dirs) (when (dired-goto-subdir hdir) (dired-hide-subdir 1)))))))) + +(defun diredp-dired-union-interactive-spec (string &optional no-dired-bufs read-extra-files-p dired-buffer) + "Read arguments for `diredp-dired-union' and `diredp-add-to-dired-buffer'. +STRING is appended to the prompt for the listing buffer name. +Non-nil NO-DIRED-BUFS means do not read Dired buffers to union. +Non-nil READ-EXTRA-FILES-P is passed to `dired-read-dir-and-switches', + and means read extra files to add to the listing. +Non-nil DIRED-BUFFER is passed to `dired-read-dir-and-switches'. + It is the name of the Dired union buffer." + (let* ((current-prefix-arg -1) + (dir+switches (dired-read-dir-and-switches string read-extra-files-p dired-buffer)) + (dirname (car dir+switches)) + (switches (cadr dir+switches)) + (dirbufs ()) + (bufs ()) + (extra-files ()) + buf) + (when (consp dirname) (setq extra-files (cdr dirname) + dirname (car dirname))) + (unless no-dired-bufs + ;; Remove any killed buffers from `dired-buffers'. Then use all but the target buffer as candidates. + (dolist (db dired-buffers) + (if (buffer-live-p (cdr db)) + (unless (equal dirname (buffer-name (cdr db))) + (push (cons (buffer-name (cdr db)) (car db)) dirbufs)) + (setq dired-buffers (delq db dired-buffers)))) + (while (and dirbufs (condition-case nil + (setq buf (completing-read "Existing Dired buffer to include (C-g when done): " + dirbufs nil t nil 'buffer-name-history + (and dirbufs (car (assoc (buffer-name) dirbufs))))) + (quit nil))) + (push buf bufs) + (setq dirbufs (delete (cons buf (with-current-buffer buf (expand-file-name default-directory))) + dirbufs))) + (setq bufs (nreverse bufs))) + (list dirname bufs switches extra-files))) + +(when (> emacs-major-version 23) ; `compilation--loc->file-struct' + + (defalias 'diredp-grepped-files-other-window 'diredp-compilation-files-other-window) + (defun diredp-compilation-files-other-window (&optional switches) + "Open Dired on the files indicated by compilation (e.g., `grep') hits. +Applies to any `compilation-mode'-derived buffer, such as `*grep*'. +You are prompted for the name of the new Dired buffer. +With a prefix arg you are first prompted for the `ls' switches. + +\(However, Emacs bug #20739 means that the switches are ignored.)" + (interactive (list (and current-prefix-arg (read-string "Dired listing switches: " dired-listing-switches)))) + (unless (compilation-buffer-p (current-buffer)) (error "Not in a buffer derived from `compilation-mode'")) + (let ((files ())) + (save-excursion (goto-char (point-min)) + (while (condition-case nil (compilation-next-file 1) (error nil)) + (setq compilation-current-error (point)) + (push (diredp-file-for-compilation-hit-at-point) files))) + (setq files (nreverse files)) + (dired-other-window + (cons (read-string "Dired buffer name: " nil nil (generate-new-buffer-name default-directory)) files) + switches))) + + (defun diredp-file-for-compilation-hit-at-point () + "Return the name of the file for the compilation hit at point. +The name is expanded in the directory for the last directory change." + (let* ((msg (compilation-next-error 0)) + (loc (compilation--message->loc msg)) + (filestruct (compilation--loc->file-struct loc)) + (file (caar filestruct)) + (dir (cadr (car filestruct)))) + (when dir (setq file (expand-file-name file dir))) + file)) + ) + +;;;###autoload +(defun diredp-fileset (flset-name) ; Bound to `C-x D S' + "Open Dired on the files in fileset FLSET-NAME." + (interactive + (progn (unless (require 'filesets nil t) (error "Feature `filesets' not provided")) + (unless filesets-data (error "`filesets-data' is empty")) + (list (completing-read "Open Dired on fileset: " filesets-data)))) + (diredp-fileset-1 flset-name)) + +;;;###autoload +(defun diredp-fileset-other-window (flset-name) ; Bound to `C-x 4 D S' + "Open Dired in another window on the files in fileset FLSET-NAME." + (interactive + (progn (unless (require 'filesets nil t) (error "Feature `filesets' not provided")) + (unless filesets-data (error "`filesets-data' is empty")) + (list (completing-read "Open Dired on fileset, in other window: " filesets-data)))) + (diredp-fileset-1 flset-name 'OTHER-WINDOW)) + +(defun diredp-fileset-1 (flset-name &optional other-window-p) + "Helper for `diredp-fileset(-other-window)'." + (let ((flset (filesets-get-fileset-from-name flset-name)) + (files ()) + (mode nil) + (dirfun (if other-window-p #'dired-other-window #'dired))) + (unless (or (setq mode (filesets-entry-mode flset)) ; ("my-fs" (:files "a" "b")) + (setq flset (cons "dummy" flset) ; (:files "a" "b") + mode (filesets-entry-mode flset))) + (error "Bad fileset: %S" flset-name)) + (message "Gathering file names...") + (dolist (file (filesets-get-filelist flset mode)) (push file files)) + (funcall dirfun (cons (generate-new-buffer-name flset-name) + (nreverse (mapcar (lambda (file) + (if (file-name-absolute-p file) + (expand-file-name file) + file)) + files)))))) + +;;;###autoload +(defun diredp-dired-this-subdir (&optional tear-off-p msgp) + "Open Dired for the subdir at or above point. +If point is not on a subdir line, but is in an inserted subdir +listing, then use that subdir. + +With a prefix arg: + If the subdir is inserted and point is in the inserted listing then + remove that listing and move to the ordinary subdir line. In other + words, when in an inserted listing, a prefix arg tears off the + inserted subdir to its own Dired buffer." + (interactive "P\np") + (diredp-ensure-mode) + (let* ((this-dir default-directory) + (this-subdir (diredp-this-subdir)) + (on-dir-line-p (atom this-subdir))) + (unless on-dir-line-p ; Subdir header line or non-directory file. + (setq this-subdir (car this-subdir))) + (unless (string= this-subdir this-dir) + (when tear-off-p + (unless on-dir-line-p + (dired-kill-subdir) ; Tear it off. + (dired-goto-file this-subdir))) ; Move to normal subdir line. + (dired-other-window this-subdir)))) + +;;;###autoload +(defun diredp-dired-inserted-subdirs (&optional no-show-p msgp) ; Bound to `C-M-i' + "Open Dired for each of the subdirs inserted in this Dired buffer. +A separate Dired buffer is used for each of them. +With a prefix arg, create the Dired buffers but do not display them. +Markings and current Dired switches are preserved." + (interactive "P\np") + (diredp-ensure-mode) + (let ((this-dir default-directory) + (this-buff (current-buffer)) + (this-frame (selected-frame)) + marked) + (unwind-protect + (save-selected-window + (dolist (entry dired-subdir-alist) + (unless (string= (car entry) this-dir) + (setq marked (with-current-buffer this-buff + (dired-remember-marks (dired-get-subdir-min entry) (dired-get-subdir-max entry)))) + (if (not no-show-p) + (dired-other-window (car entry) dired-actual-switches) + (dired-noselect (car entry) dired-actual-switches) + (when msgp (message "Dired buffers created but not shown"))) + (set-buffer this-buff) + (let ((inhibit-read-only t)) + (dired-mark-remembered marked)) + (set-buffer-modified-p nil)))) + (select-frame-set-input-focus this-frame)))) + + +;;; Actions on marked files and subdirs, recursively. + +(defun diredp-get-subdirs (&optional ignore-marks-p predicate details) + "Return subdirs from this Dired buffer and from marked subdirs, recursively. +If optional arg IGNORE-MARKS-P is non-nil then include all +subdirectories. Otherwise, include only those that are marked. + +Non-nil optional arg PREDICATE means include only subdirectory names +for which the PREDICATE returns non-nil. PREDICATE must accept a file +name as its only required argument. + +Optional arg DETAILS is passed to `diredp-get-files'." + (diredp-get-files ignore-marks-p (if predicate + `(lambda (name) (and (file-directory-p name) (funcall ,predicate name))) + #'file-directory-p) + 'INCLUDE-DIRS-P 'DONT-ASKP 'ONLY-MARKED-P details)) + +(defun diredp-get-files (&optional ignore-marks-p predicate include-dirs-p dont-askp only-marked-p details) + "Return file names from this Dired buffer and subdirectories, recursively. +The names are those that are marked in the current Dired buffer, or +all files in the directory if none are marked. Marked subdirectories +are handled recursively in the same way. + +If there is some included subdirectory that has a Dired buffer with +marked files, then (unless DONT-ASKP is non-nil) this asks you whether +to use the marked files in Dired buffers, as opposed to using all of +the files in included directories. To this y-or-n question you can +hit `l' to see the list of files that will be included (using +`diredp-list-files'). In that `l' listing you can mouseover to see +image-file previews or use `RET' or `mouse-2' to visit files. + +\(Directories in `icicle-ignored-directories' are skipped, if you use +Icicles. Otherwise, directories in `vc-directory-exclusion-list' are +skipped.) + +Non-nil IGNORE-MARKS-P means ignore all Dired markings: just get all +of the files in the current directory (and all of the subdirectories, +if INCLUDE-DIRS-P is non-nil). + +Non-nil PREDICATE means include only file names for which the +PREDICATE returns non-nil. PREDICATE must accept a file name as its +only required argument. + +Non-nil INCLUDE-DIRS-P means include marked subdirectory names (but +also handle those subdirs recursively, picking up their marked files +and subdirs). + +Non-nil DONT-ASKP means do not ask the user whether to use marked +instead of all. Act as if the user was asked and replied `y'. + +Non-nil optional arg ONLY-MARKED-P means collect only marked files, +instead of collecting all files if none are marked. This argument is +ignored if IGNORE-MARKS-P is non-nil. + +Optional arg DETAILS is passed to `diredp-y-or-n-files-p'." + (let ((askp (list nil))) ; The cons's car will be set to `t' if need to ask user. + (if ignore-marks-p + (diredp-files-within (directory-files default-directory 'FULL diredp-re-no-dot) + () nil include-dirs-p predicate) + ;; Pass FILES and ASKP to `diredp-get-files-for-dir', so we don't have to use them as + ;; free vars there. But that means that they each need to be a cons cell that we can + ;; modify, so we can get back the updated info. + (let ((files (list 'DUMMY))) ; The files picked up will be added to this list. + (diredp-get-files-for-dir default-directory files askp include-dirs-p only-marked-p) + (setq files (cdr files)) ; Remove `DUMMY' from the modifed list. + (if (or dont-askp + (not (car askp)) + (diredp-y-or-n-files-p "Use marked (instead of all) in subdir Dired buffers? " + files predicate details)) + (if predicate (diredp-remove-if-not predicate files) files) + (setq files ()) + (dolist (file (diredp-marked-here)) + (if (not (file-directory-p file)) + (when (or (not predicate) (funcall predicate file)) + (add-to-list 'files file)) + (when include-dirs-p (setq files (nconc files (list file)))) + (setq files (nconc files (diredp-files-within (directory-files file 'FULL diredp-re-no-dot) + () nil include-dirs-p predicate))))) + (nreverse files)))))) + +(defun diredp-get-files-for-dir (directory accum askp &optional include-dirs-p only-marked-p) + "Return marked file names for DIRECTORY and subdirectories, recursively. +Pick up names of all marked files in DIRECTORY if it has a Dired +buffer, or all files in DIRECTORY if not. Handle subdirs recursively +\(only marked subdirs, if Dired). + +ACCUM is an accumulator list: the files picked up in this call are +nconc'd to it. + +ASKP is a one-element list, the element indicating whether to ask the +user about respecting Dired markings. It is set here to `t' if there +is a Dired buffer for DIRECTORY. + +Non-nil optional arg INCLUDE-DIRS-P means include marked subdirectory +names (but also handle those subdirs recursively). + +Non-nil optional arg ONLY-MARKED-P means collect only marked files, +instead of collecting all files if none are marked. + +If there is more than one Dired buffer for DIRECTORY then raise an +error." + (let ((dbufs (dired-buffers-for-dir (expand-file-name directory)))) + (dolist (file (if (not dbufs) + (and (not only-marked-p) (directory-files directory 'FULL diredp-re-no-dot)) + (when (cadr dbufs) (error "More than one Dired buffer for `%s'" directory)) + (unless (equal directory default-directory) (setcar askp t)) + (with-current-buffer (car dbufs) (diredp-marked-here only-marked-p 'NO-DOT-DOT)))) + (if (not (file-directory-p file)) + (setcdr (last accum) (list file)) + (when include-dirs-p (setcdr (last accum) (list file))) + (diredp-get-files-for-dir file accum askp include-dirs-p only-marked-p))))) + +(defun diredp-marked-here (&optional only-marked-p no-dot-dot-p) + "Marked files and subdirs in this Dired buffer, or all if none are marked. +Non-nil optional arg ONLY-MARKED-P means return nil if none are +marked. +Non-nil optional arg NO-DOT-DOT-P means do not include marked `..'." + ;; If no file is marked, exclude `(FILENAME)': the unmarked file at cursor. + ;; If there are no marked files as a result, return all files and subdirs in the dir. + (let* ((dired-marker-char ?*) + (ff (condition-case nil ; Ignore error if on `.' or `..' and no file is marked. + (dired-get-marked-files + nil nil (and no-dot-dot-p + (lambda (mf) (not (diredp-string-match-p "/\\.\\.$" mf)))) + 'DISTINGUISH-ONE-MARKED) + (error nil)))) + (cond ((eq t (car ff)) (cdr ff)) ; Single marked + ((cadr ff) ff) ; Multiple marked + (t (and (not only-marked-p) ; None marked + (directory-files default-directory 'FULL diredp-re-no-dot 'NOSORT)))))) + +(defun diredp-y-or-n-files-p (prompt files &optional predicate details) + "PROMPT user with a \"y or n\" question about a list of FILES. +Return t if answer is \"y\". Otherwise, return nil. + +Like `y-or-n-p', but you can also hit `l' to display the list of files +that the confirmation is for, in buffer `*Files'. In that `'l' +listing you can mouseover to see image-file previews or use `RET' or +`mouse-2' to visit files. + +When finished, buffer `*Files*' is killed if it was never shown, or is +hidden and buried otherwise. Thus, if it was shown then it is still +available to revisit afterward (even if you quit using `C-g'). + +PREDICATE is passed to `diredp-list-files', to list only file names +for which it returns non-nil. + +DETAILS is passed to `diredp-list-files', to show details about FILES." + (let ((answer 'recenter)) + (cond (noninteractive + (setq prompt (concat prompt + (and (not (eq ?\ (aref prompt (1- (length prompt))))) " ") + "(y or n; l to show file list) ")) + (let ((temp-prompt prompt)) + (while (not (memq answer '(act skip))) + (let ((str (read-string temp-prompt))) + (cond ((member str '("y" "Y")) (setq answer 'act)) + ((member str '("n" "N")) (setq answer 'skip)) + (t (setq temp-prompt (concat "Please answer y or n. " prompt)))))))) + ((if (not (fboundp 'display-popup-menus-p)) + (and window-system (listp last-nonmenu-event) use-dialog-box) + (and (display-popup-menus-p) (listp last-nonmenu-event) use-dialog-box)) + (setq answer (x-popup-dialog t `(,prompt ("Yes" . act) ("No" . skip))))) + (t + (let ((list-buf (generate-new-buffer-name "*Files*")) + (list-was-shown nil)) + (unwind-protect + (progn + (define-key query-replace-map "l" 'show) + (setq prompt (concat prompt + (and (eq ?\ (aref prompt (1- (length prompt)))) + "" " ") + "(y or n; l to show file list) ")) + (while (let* ((reprompt-actions '(recenter scroll-up scroll-down + scroll-other-window scroll-other-window-down)) + (key (let ((cursor-in-echo-area t)) + (when minibuffer-auto-raise + (raise-frame (window-frame (minibuffer-window)))) + (if (fboundp 'read-key) + (read-key (propertize + (if (memq answer reprompt-actions) + prompt + (concat "Please answer y or n. " prompt)) + 'face 'minibuffer-prompt)) + (read-char-exclusive + (if (memq answer reprompt-actions) + prompt + (concat "Please answer y or n. " prompt))))))) + (setq answer (lookup-key query-replace-map (vector key) t)) + (case answer + ((skip act) nil) + (recenter (recenter) t) + (show (diredp-list-files files nil list-buf predicate details) + (setq list-was-shown t)) ; Record showing it. + (help (message "Use `l' to show file list") (sit-for 1)) + (scroll-up (condition-case nil (scroll-up-command) (error nil)) t) + (scroll-down (condition-case nil (scroll-down-command) (error nil)) t) + (scroll-other-window (condition-case nil (scroll-other-window) (error nil)) t) + (scroll-other-window-down (condition-case nil (scroll-other-window-down nil) + (error nil)) t) + ((exit-prefix quit) (signal 'quit nil) t) + (t (or (not (eq key ?\e)) (progn (signal 'quit nil) t))))) + (ding) + (discard-input))) + (when (get-buffer list-buf) + (save-window-excursion (pop-to-buffer list-buf) + (condition-case nil ; Ignore error if user already deleted. + (if (one-window-p) (delete-frame) (delete-window)) + (error nil)) + (if list-was-shown (bury-buffer list-buf) (kill-buffer list-buf)))) + (define-key query-replace-map "l" nil))))) + (let ((ret (eq answer 'act))) + (unless noninteractive (message "%s %s" prompt (if ret "y" "n"))) + ret))) + +(defvar diredp-list-files-map + (let ((map (make-sparse-keymap))) + (define-key map "q" 'quit-window) + (define-key map "\r" 'diredp-find-line-file-other-window) + (define-key map [mouse-2] 'diredp-mouse-find-line-file-other-window) + map) + "Keymap for `diredp-list-files' output.") +(fset 'diredp-list-files-map diredp-list-files-map) + +;;;###autoload +(defun diredp-find-line-file-other-window () + "Visit file named by current line, in another window. +The full text of the line is used as the file name." + (interactive) + (let ((file (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) + (when file (find-file-other-window file)))) + +;;;###autoload +(defun diredp-mouse-find-line-file-other-window (e) + "Visit file named by clicked line, in another window. +The full text of the line is used as the file name." + (interactive "e") + (save-excursion (mouse-set-point e) (diredp-find-line-file-other-window))) + +;;;###autoload +(defun diredp-list-marked (&optional arg predicate interactivep details) ; Bound to `C-M-l' + "List the marked files in this Dired buffer. +A prefix arg specifies files to use instead of the marked files: + + * Numeric prefix arg N: The next N files (previous -N, if < 0). + * C-u C-u: All files, but no directories. + * C-u C-u C-u: All files and directories, except `.' and `..' + * C-u C-u C-u C-u: All files and directories, including `.' and `..' + * Any other prefix arg: The current line's file only. + +You can use `RET' or `mouse-2' to visit any of the files. +If `tooltip-mode' is on then moving the mouse over image-file names +shows image previews. + +When called from Lisp: + Non-nil optional arg PREDICATE is a file-name predicate. List only + the files for which it returns non-nil. + Non-nil optional arg DETAILS is passed to `diredp-list-files'." + (interactive (progn (diredp-ensure-mode) (list current-prefix-arg nil t diredp-list-file-attributes))) + (let ((files (dired-get-marked-files nil arg predicate 'DISTINGUISH-ONE interactivep))) + (diredp-list-files files nil nil nil details))) + +(defun diredp-list-files (files &optional dir bufname predicate details) + "Display FILES, a list of file names. Wildcard patterns are expanded. +The files are shown in a new buffer, `*Files*' by default. + +Optional arg DIR serves as the default directory for expanding file + names that are not absolute. It defaults to `default-directory'. + +Optional arg BUFNAME is the name of the buffer for the display. + It defaults to `*Files*' (or `*Files*' if `*Files*' exists). + +Optional arg PREDICATE is a predicate used to filter FILES: only files + satisfying PREDICATE are listed. + +Non-nil arg DETAILS means show details about each file, in addition to +the file name. It is passed to `diredp-list-file' (which see). + +File names listed are absolute. Mouseover gives help or an image-file +preview, and you can use `RET' or `mouse-2' to visit files." + (unless bufname (setq bufname (generate-new-buffer-name "*Files*"))) + (diredp-with-help-window + bufname + (princ "Files\n-----\n\n") + (let ((all-files-no-wildcards ()) + file-alist file-dir) + (dolist (file files) + (unless (or (string= file "") ; Ignore empty file names. + (and predicate (not (funcall predicate file)))) + (if (not (diredp-string-match-p "[[?*]" file)) + (add-to-list 'all-files-no-wildcards (diredp-list-file file details)) + (setq file-dir (or (file-name-directory file) default-directory) + file-alist (directory-files-and-attributes file-dir 'FULL "[[?*]" 'NOSORT)) + (dolist (ff file-alist) + (add-to-list 'all-files-no-wildcards (diredp-list-file file details)))))) + (save-excursion (dolist (fff (nreverse all-files-no-wildcards)) + (princ fff) (terpri))))) + (with-current-buffer bufname + (let ((buffer-read-only nil)) + (save-excursion + (goto-char (point-min)) + (forward-line 3) + (while (not (eobp)) + (add-text-properties (line-beginning-position) (line-end-position) + '(mouse-face highlight help-echo diredp-mouseover-help dired-filename t + ;; `keymap' does not work for Emacs 20. Could use `local-map' + ;; but that still leaves `RET' bound to `help-follow'. + keymap diredp-list-files-map)) + (forward-line 1)))) + (set-buffer-modified-p nil) + (setq buffer-read-only t) + (buffer-enable-undo))) + +(defun diredp-list-file (file &optional details) + "Return FILE name, expanded. +Non-nil optional arg DETAILS means append details about FILE to the +returned string. + +If DETAILS is a list of file attribute numbers then include only the +values of those attributes. Otherwise, include all attribute values." + (let ((file-dir (and details (or (file-name-directory file) default-directory))) + attrs) + (setq file (expand-file-name file file-dir)) + (when (and details (atom details)) (setq details '(0 1 2 3 4 5 6 7 8 9 10 11))) + (concat + file + (and details + (setq attrs (file-attributes file)) + (concat + "\n" + (and (memq 0 details) + (format " File Type: %s\n" + (cond ((eq t (nth 0 attrs)) "Directory") + ((stringp (nth 0 attrs)) (format "Symbolic link to `%s'" (nth 0 attrs))) + (t "Normal file")))) + (and (memq 8 details) + (format " Permissions: %s\n" (nth 8 attrs))) + (and (memq 7 details) (not (eq t (nth 0 attrs))) + (format " Size in bytes: %g\n" (nth 7 attrs))) + (and (memq 4 details) + (format-time-string " Time of last access: %a %b %e %T %Y (%Z)\n" (nth 4 attrs))) + (and (memq 5 details) + (format-time-string " Time of last modification: %a %b %e %T %Y (%Z)\n" (nth 5 attrs))) + (and (memq 6 details) + (format-time-string " Time of last status change: %a %b %e %T %Y (%Z)\n" (nth 6 attrs))) + (and (memq 1 details) + (format " Number of links: %d\n" (nth 1 attrs))) + (and (memq 2 details) + (format " User ID (UID): %s\n" (nth 2 attrs))) + (and (memq 3 details) + (format " Group ID (GID): %s\n" (nth 3 attrs))) + (and (memq 10 details) + (format " Inode: %S\n" (nth 10 attrs))) + (and (memq 11 details) + (format " Device number: %s\n" (nth 11 attrs)))))))) + +(defvar diredp-files-within-dirs-done () + "Directories already processed by `diredp-files-within'.") + + +;; Not used in the `Dired+' code yet. +(defun diredp-directories-within (&optional directory no-symlinks-p predicate) + "List of accessible directories within DIRECTORY. +Directories in `icicle-ignored-directories' are skipped, if you use +Icicles. Otherwise, directories in `vc-directory-exclusion-list' are +skipped. + +Optional arg DIRECTORY defaults to the value of `default-directory'. +Non-nil optional arg NO-SYMLINKS-P means do not follow symbolic links. +Non-nil optional arg PREDICATE must be a function that accepts a + file-name argument. Only directories that satisfy PREDICATE are + included in the result." + (unless directory (setq directory default-directory)) + (let ((dirs (diredp-files-within (directory-files directory 'FULL diredp-re-no-dot) + () no-symlinks-p 'INCLUDE-DIRS-P + #'file-directory-p))) + (if predicate (diredp-remove-if-not predicate dirs) dirs))) + +;; Args INCLUDE-DIRS-P and PREDICATE are not used in the `Dired+' code yet +;; (except in `diredp-directories-within', which also is not used yet). +;; +(defun diredp-files-within (file-list accum &optional no-symlinks-p include-dirs-p predicate) + "List of readable files in FILE-LIST, handling directories recursively. +FILE-LIST is a list of file names or a function that returns such. +If a function then invoke it with no args to get the list of files. + +Accessible directories in the list of files are processed recursively +to include their files and the files in their subdirectories. The +directories themselves are not included, unless optional arg +INCLUDE-DIRS-P is non-nil. (Directories in +`icicle-ignored-directories' are skipped, if you use Icicles. +Otherwise, directories in `vc-directory-exclusion-list' are skipped.) + +But if there is a Dired buffer for such a directory, and if FILE-LIST +is a function, then it is invoked in that Dired buffer to return the +list of files to use. E.g., if FILE-LIST is `dired-get-marked-files' +then only the marked files and subdirectories are included. If you +have more than one Dired buffer for a directory that is processed +here, then only the first one in `dired-buffers' is used. + +The list of files is accumulated in ACCUM, which is used for recursive +calls. + +Non-nil optional arg NO-SYMLINKS-P means do not follow symbolic links. + +Non-nil optional arg INCLUDE-DIRS-P means include directory names +along with the names of non-directories. + +Non-nil optional arg PREDICATE must be a function that accepts a +file-name argument. Only files (and possibly directories) that +satisfy PREDICATE are included in the result." + ;; Bind `diredp-files-within-dirs-done' for use as a free var in `diredp-files-within-1'. + (let ((diredp-files-within-dirs-done ())) + (nreverse (diredp-files-within-1 file-list accum no-symlinks-p include-dirs-p predicate)))) + +;; `diredp-files-within-dirs-done' is free here, bound in `diredp-files-within'. +(defun diredp-files-within-1 (file-list accum no-symlinks-p include-dirs-p predicate) + "Helper for `diredp-files-within'." + (let ((files (if (functionp file-list) (funcall file-list) file-list)) + (res accum) + file) + (when (and files predicate) (setq files (diredp-remove-if-not predicate files))) + (while files + (setq file (car files)) + (unless (and no-symlinks-p (file-symlink-p file)) + (if (file-directory-p file) + ;; Skip directory if ignored, already treated, or inaccessible. + (when (and (not (member (file-name-nondirectory file) + (if (boundp 'icicle-ignored-directories) + icicle-ignored-directories + (and (boundp 'vc-directory-exclusion-list) + vc-directory-exclusion-list)))) + (not (member (file-truename file) diredp-files-within-dirs-done)) + (file-accessible-directory-p file)) + (setq res (diredp-files-within-1 (or (and (functionp file-list) + (dired-buffers-for-dir + (expand-file-name file)) ; Removes killed buffers. + (with-current-buffer + (cdr (assoc (file-name-as-directory file) + dired-buffers)) + (funcall file-list))) + (directory-files file 'FULL diredp-re-no-dot)) + res no-symlinks-p include-dirs-p predicate)) + (when include-dirs-p (push file res)) + (push (file-truename file) diredp-files-within-dirs-done)) + (when (file-readable-p file) (push file res)))) + (pop files)) + res)) + +(defun diredp-remove-if (pred xs) + "A copy of list XS with no elements that satisfy predicate PRED." + (let ((result ())) + (dolist (x xs) (unless (funcall pred x) (push x result))) + (nreverse result))) + +(defun diredp-remove-if-not (pred xs) + "A copy of list XS with only elements that satisfy predicate PRED." + (let ((result ())) + (dolist (x xs) (when (funcall pred x) (push x result))) + (nreverse result))) + +(when (> emacs-major-version 21) ; Emacs 20 has no PREDICATE arg to `read-file-name'. + (defun diredp-insert-as-subdir (child ancestor &optional in-dired-now-p) + "Insert the current Dired dir into a Dired listing of an ancestor dir. +Ancestor means parent, grandparent, etc. at any level. +You are prompted for the ancestor directory. +The ancestor Dired buffer is selected. + +Markings and switches in the current Dired buffer are preserved for +the subdir listing in the ancestor Dired buffer. + +Note: If you use Icicles, then you can use +`icicle-dired-insert-as-subdir' instead: it is a multi-command. It +does the same thing, but it lets you insert any number of descendant +directories into a given ancestor-directory Dired buffer. + +Non-interactively: + Insert CHILD dir into Dired listing for ANCESTOR dir. + + Non-nil optional arg IN-DIRED-NOW-P means to use the current buffer + as the Dired buffer from which to pick up markings and switches. + Otherwise, pick them up from a Dired buffer for CHILD, if there is + exactly one such buffer." + (interactive (progn (diredp-ensure-mode) + (list default-directory + (completing-read + "Insert this dir into ancestor dir: " + (mapcar #'list (diredp-ancestor-dirs default-directory))) + t))) + (let ((child-dired-buf (if in-dired-now-p + (current-buffer) + (dired-buffers-for-dir (expand-file-name child)))) + (switches ()) + (marked ())) + (when (consp child-dired-buf) + (setq child-dired-buf (and (= 1 (length child-dired-buf)) (car child-dired-buf)))) + (when child-dired-buf + (with-current-buffer child-dired-buf + (setq switches dired-actual-switches + marked (dired-remember-marks (point-min) (point-max))))) + (dired-other-window ancestor) + (dired-insert-subdir child switches) + (when marked (let ((inhibit-read-only t)) (dired-mark-remembered marked))) + (set-buffer-modified-p nil)))) + +(defun diredp-ancestor-dirs (dir) + "Return a list of the ancestor directories of directory DIR." + (mapcar #'file-name-as-directory + (diredp-maplist (lambda (dd) (mapconcat #'identity (reverse dd) "/")) + (cdr (nreverse (split-string dir "/" t)))))) + +(defun diredp-maplist (function list) + "Map FUNCTION over LIST and its cdrs. +A simple, recursive version of the classic `maplist'." + (and list (cons (funcall function list) (diredp-maplist function (cdr list))))) + +(defun diredp-existing-dired-buffer-p (buffer-name) + "Return non-nil if BUFFER-NAME names a live, existing Dired buffer." + (let ((dbuf (get-buffer buffer-name))) + (and dbuf (buffer-live-p dbuf) (rassq dbuf dired-buffers)))) + +;; From `cl-seq.el', function `union', without keyword treatment. +;; (Same as `icicle-set-union' in `icicles-fn.el'.) +(defun diredp-set-union (list1 list2) + "Combine LIST1 and LIST2 using a set-union operation. +The result list contains all items that appear in either LIST1 or +LIST2. Comparison is done using `equal'. This is a non-destructive +function; it copies the data if necessary." + (cond ((null list1) list2) + ((null list2) list1) + ((equal list1 list2) list1) + (t + (unless (>= (length list1) (length list2)) + (setq list1 (prog1 list2 (setq list2 list1)))) ; Swap them. + (while list2 + (unless (member (car list2) list1) (setq list1 (cons (car list2) list1))) + (setq list2 (cdr list2))) + list1))) + +(when (fboundp 'file-equal-p) ; Emacs 24+ + (defun diredp-move-file (file &optional prompt-anyway) + "Move FILE to associated directory in `diredp-move-file-dirs'. +If no association, or if you use a prefix arg, prompt for directory." + (interactive (list (dired-get-filename) current-prefix-arg)) + (unless file (error "No file specified")) + (let* ((file-sans (file-name-nondirectory file)) + (dir (file-name-as-directory + (or (and (not prompt-anyway) + (cdr (assoc file-sans diredp-move-file-dirs))) + (read-directory-name "Move to: "))))) + (when (file-equal-p dir (file-name-directory file)) + (error "Cannot move to same directory: %s" dir)) + (dired-rename-file file dir nil) + (dired-add-file (expand-file-name file-sans dir)) + (message "Moved `%s' to `%s'" file-sans dir)))) + +(defvar diredp-last-copied-filenames () + "String list of file names last copied to the `kill-ring'. +Copying is done by `dired-copy-filename-as-kill' and related commands.") + + +;; REPLACE ORIGINAL in `dired-x.el'. +;; +;; Put text copied to kill ring in variable `diredp-last-copied-filenames'. +;; +(defun dired-copy-filename-as-kill (&optional arg) + "Copy names of marked (or next ARG) files into the kill ring. +The names are separated by a space. +With a zero prefix arg, use the absolute file name of each marked file. +With \\[universal-argument], use the file name relative to the Dired buffer's +`default-directory'. (This still may contain slashes if in a subdirectory.) + +If on a subdir headerline, use absolute subdirname instead; +prefix arg and marked files are ignored in this case. + +You can then feed the file name(s) to other commands with \\[yank]. + +The value of global variable `diredp-last-copied-filenames' is updated +to the string list of file name(s), so you can obtain it even after +the kill ring is modified." + (interactive "P") + (let* ((num-arg (prefix-numeric-value arg)) + (string (or (dired-get-subdir) + (mapconcat #'identity + (cond ((not arg) (dired-get-marked-files 'no-dir)) + ((zerop num-arg) (dired-get-marked-files)) + ((consp arg) (dired-get-marked-files t)) + (t (dired-get-marked-files 'no-dir num-arg))) + " ")))) + (unless (string= "" string) + (if (eq last-command 'kill-region) (kill-append string nil) (kill-new string)) + (setq diredp-last-copied-filenames (car kill-ring-yank-pointer)) + (message "%s" string)))) + +(defun diredp-copy-abs-filenames-as-kill () ; Not bound. + "Copy absolute names of marked files in Dired to the kill ring. +Also set variable `diredp-last-copied-filenames' to the string that +lists the file names. + +This is the same as using a zero prefix arg with command +`dired-copy-filename-as-kill', that is, \\`M-0 \\[dired-copy-filename-as-kill]'." + (interactive (diredp-ensure-mode)) + (dired-copy-filename-as-kill 0)) + +;;;###autoload +(defalias 'diredp-paste-files 'diredp-yank-files) ; Bound to `C-y'. +;;;###autoload +(defun diredp-yank-files (&optional dir no-confirm-p details) + "Paste files, whose absolute names you copied, to the current directory. +With a non-negative prefix arg you are instead prompted for the target + directory. +With a non-positive prefix arg you can see details about the files if + you hit `l' when prompted to confirm pasting. Otherwise you see only + the file names. The details you see are defined by option + `diredp-list-file-attributes'. + +You should have copied the list of file names as a string to the kill +ring using \\`M-0 \\[dired-copy-filename-as-kill]' or \ +\\[diredp-copy-abs-filenames-as-kill]. +Those commands also set variable `diredp-last-copied-filenames' to the +same string. `diredp-yank-files' uses the value of that variable, not +whatever is currently at the head of the kill ring. + +When called from Lisp: + +Optional arg NO-CONFIRM-P means do not ask for confirmation to copy. +Optional arg DETAILS is passed to `diredp-y-or-n-files-p'." + (interactive (list (and current-prefix-arg (natnump (prefix-numeric-value current-prefix-arg)) + (expand-file-name (read-directory-name "Yank files to directory: "))) + nil + (and current-prefix-arg + (<= (prefix-numeric-value current-prefix-arg) 0) + diredp-list-file-attributes))) + (setq dir (or dir (and (derived-mode-p 'dired-mode) (dired-current-directory)))) + (unless (file-directory-p dir) (error "Not a directory: `%s'" dir)) + (let ((files diredp-last-copied-filenames)) + (unless (stringp files) (error "No copied file names")) + (setq files (diredp-delete-if-not (lambda (file) (file-name-absolute-p file)) (split-string files))) + (unless files (error "No copied *absolute* file names (Did you use `M-0 w'?)")) + (if (and (not no-confirm-p) + (diredp-y-or-n-files-p "Paste files whose names you copied? " files nil details)) + (dired-create-files #'dired-copy-file "Copy" files + (lambda (from) (expand-file-name (file-name-nondirectory from) dir))) + (message "OK, file-pasting canceled")))) + +;;;###autoload +(defun diredp-move-files-named-in-kill-ring (&optional dir no-confirm-p details) ; Bound to `C-w' + "Move files, whose absolute names you copied, to the current directory. +With a non-negative prefix arg you are instead prompted for the target + directory. +With a non-positive prefix arg you can see details about the files if + you hit `l' when prompted to confirm pasting. Otherwise you see only + the file names. The details you see are defined by option + `diredp-list-file-attributes'. + +You should have copied the list of file names as a string to the kill +ring using \\`M-0 \\[dired-copy-filename-as-kill]' or \ +\\[diredp-copy-abs-filenames-as-kill]. +Those commands also set variable `diredp-last-copied-filenames' to the +same string. `diredp-move-files-named-in-kill-ring' uses the value of +that variable, not whatever is currently at the head of the kill ring. + +When called from Lisp: + +Optional arg NO-CONFIRM-P means do not ask for confirmation to move. +Optional arg DETAILS is passed to `diredp-y-or-n-files-p'." + (interactive (list (and current-prefix-arg (natnump (prefix-numeric-value current-prefix-arg)) + (expand-file-name (read-directory-name "Move files to directory: "))) + nil + (and current-prefix-arg + (<= (prefix-numeric-value current-prefix-arg) 0) + diredp-list-file-attributes))) + (setq dir (or dir (and (derived-mode-p 'dired-mode) (dired-current-directory)))) + (unless (file-directory-p dir) (error "Not a directory: `%s'" dir)) + (let ((files diredp-last-copied-filenames)) + (unless (stringp files) (error "No copied file names")) + (setq files (diredp-delete-if-not (lambda (file) (file-name-absolute-p file)) (split-string files))) + (unless files (error "No copied (absolute* file names (Did you use `M-0 w'?)")) + (if (and (not no-confirm-p) + (diredp-y-or-n-files-p "MOVE files whose names you copied? " files nil details)) + (dired-create-files #'dired-rename-file "Move" files + (lambda (from) (expand-file-name (file-name-nondirectory from) dir))) + (message "OK, file-moves canceled")))) + + +;;; Commands operating on marked at all levels below (recursively) + +(defun diredp-get-confirmation-recursive (&optional type) + "Get confirmation from user to act on all TYPE here and below. +If TYPE is nil use \"files\" in the confirmation prompt, else use TYPE. +Raise an error if not confirmed. +Raise an error first if not in Dired mode." + (diredp-ensure-mode) + (unless (y-or-n-p (format "Act on ALL %s (or all marked if any) in and UNDER this dir? " + (or type 'files))) + (error "OK, canceled"))) + +;;;###autoload +(when (> emacs-major-version 21) ; Emacs 22+ has KILL-ROOT parameter. + (defun diredp-kill-this-tree () + "Remove this subdir listing and lower listings." + (interactive) + (dired-kill-tree (dired-current-directory) nil 'KILL-ROOT))) + +;;;###autoload +(defun diredp-insert-subdirs (&optional switches interactivep) ; Bound to `M-i' + "Insert the marked subdirectories. +Like using \\`\\[dired-maybe-insert-subdir]' at each marked directory line." + (interactive (list (and current-prefix-arg + (read-string "Switches for listing: " + (or (and (boundp 'dired-subdir-switches) dired-subdir-switches) + dired-actual-switches))) + t)) + (dolist (subdir (dired-get-marked-files nil + nil + (lambda (fl) (and (file-directory-p fl) ; Exclude `.' and `..' + (not (diredp-string-match-p "/[.][.]?\\'" fl)))) + nil + interactivep)) + (dired-maybe-insert-subdir subdir switches))) + +;;;###autoload +(defun diredp-insert-subdirs-recursive (&optional ignore-marks-p details) ; Bound to `M-+ M-i' + "Insert the marked subdirs, including those in marked subdirs. +Like `diredp-insert-subdirs', but act recursively on subdirs. +The subdirs inserted are those that are marked in the current Dired +buffer, or ALL subdirs in the directory if none are marked. Marked +subdirectories are handled recursively in the same way (their marked +subdirs are inserted...). + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive 'subdirs) + (list current-prefix-arg diredp-list-file-attributes))) + (dolist (subdir (diredp-get-files ignore-marks-p #'file-directory-p 'INCLUDE-SUBDIRS-P nil nil details)) + (dired-maybe-insert-subdir subdir))) + +;;;###autoload +(defun diredp-do-shell-command-recursive (command &optional ignore-marks-p details) ; Bound to `M-+ !' + "Run shell COMMAND on the marked files, including those in marked subdirs. +Like `dired-do-shell-command', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive + (progn (diredp-get-confirmation-recursive) + (let* ((prompt "! on *: ") + (cmd (minibuffer-with-setup-hook + (lambda () + (set (make-local-variable 'minibuffer-default-add-function) + 'minibuffer-default-add-dired-shell-commands)) + (let ((dired-no-confirm t)) + (if (functionp 'dired-guess-shell-command) + ;; Guess cmd based only on files marked in current (top) dir. + (dired-guess-shell-command prompt (dired-get-marked-files t)) + (read-shell-command prompt nil nil)))))) + (list cmd current-prefix-arg diredp-list-file-attributes)))) + (dired-do-shell-command command nil (diredp-get-files ignore-marks-p nil nil nil nil details))) + +(when (fboundp 'dired-do-async-shell-command) ; Emacs 23+ + + (defun diredp-do-async-shell-command-recursive (command &optional ignore-marks-p details) + ; Bound to `M-+ &' + "Run async shell COMMAND on marked files, including in marked subdirs. +Like `dired-do-async-shell-command', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive + (progn (diredp-get-confirmation-recursive) + (let* ((prompt "! on *: ") + (cmd (minibuffer-with-setup-hook + (lambda () + (set (make-local-variable 'minibuffer-default-add-function) + 'minibuffer-default-add-dired-shell-commands)) + (let ((dired-no-confirm t)) + (if (functionp 'dired-guess-shell-command) + ;; Guess cmd based only on files marked in current (top) dir. + (dired-guess-shell-command prompt (dired-get-marked-files t)) + (read-shell-command prompt nil nil)))))) + (list cmd current-prefix-arg diredp-list-file-attributes)))) + (dired-do-async-shell-command command nil (diredp-get-files ignore-marks-p nil nil nil nil details)))) + +;;;###autoload +(defun diredp-do-symlink-recursive (&optional ignore-marks-p details) ; Bound to `M-+ S' + "Make symbolic links to marked files, including those in marked subdirs. +Like `dired-do-symlink', but act recursively on subdirs to pick up the +files to link. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-create-files-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-do-create-files-recursive #'make-symbolic-link "Symlink" ignore-marks-p details)) + +(defun diredp-do-relsymlink-recursive (&optional ignore-marks-p details) ; Bound to `M-+ Y' + "Relative symlink all marked files, including those in marked subdirs into a dir. +Like `dired-do-relsymlink', but act recursively on subdirs to pick up the +files to link. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +For absolute symlinks, use \\[diredp-do-symlink-recursive]. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-create-files-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (diredp-do-create-files-recursive #'dired-make-relative-symlink "RelSymLink" ignore-marks-p details)) + +;;;###autoload +(defun diredp-do-hardlink-recursive (&optional ignore-marks-p details) ; Bound to `M-+ H' + "Add hard links for marked files, including those in marked subdirs. +Like `dired-do-hardlink', but act recursively on subdirs to pick up the +files to link. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-create-files-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-do-create-files-recursive #'dired-hardlink "Hardlink" ignore-marks-p details)) + +;;;###autoload +(defun diredp-do-print-recursive (&optional ignore-marks-p details) ; Bound to `M-+ P' + "Print the marked files, including those in marked subdirs. +Like `dired-do-print', but act recursively on subdirs to pick up the +files to print. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (let* ((file-list (diredp-get-files ignore-marks-p nil nil nil nil details)) + (command (dired-mark-read-string + "Print %s with: " + (mapconcat #'identity + (cons lpr-command (if (stringp lpr-switches) (list lpr-switches) lpr-switches)) + " ") + 'print nil file-list))) + (dired-run-shell-command (dired-shell-stuff-it command file-list nil)))) + +;;;###autoload +(defun diredp-image-dired-display-thumbs-recursive (&optional ignore-marks-p append do-not-pop details) + ; Bound to `M-+ C-t d' + "Display thumbnails of marked files, including those in marked subdirs. +Like `image-dired-display-thumbs', but act recursively on subdirs. +Optional arguments APPEND and DO-NOT-POP are as for +`image-dired-display-thumbs'. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-image-dired-required-msg) + (diredp-get-confirmation-recursive) + (list current-prefix-arg nil nil diredp-list-file-attributes))) + (let ((buf (image-dired-create-thumbnail-buffer)) + thumb-name files dired-buf) + (setq files (diredp-get-files ignore-marks-p nil nil nil nil details) + dired-buf (current-buffer)) + (with-current-buffer buf + (let ((inhibit-read-only t)) + (if append (goto-char (point-max)) (erase-buffer)) + (mapc (lambda (curr-file) + (setq thumb-name (image-dired-thumb-name curr-file)) + (if (and (not (file-exists-p thumb-name)) + (not (= 0 (image-dired-create-thumb curr-file thumb-name)))) + (message "Thumb could not be created for file %s" curr-file) + (image-dired-insert-thumbnail thumb-name curr-file dired-buf))) + files)) + (case image-dired-line-up-method + (dynamic (image-dired-line-up-dynamic)) + (fixed (image-dired-line-up)) + (interactive (image-dired-line-up-interactive)) + (none nil) + (t (image-dired-line-up-dynamic)))) + (if do-not-pop + (display-buffer image-dired-thumbnail-buffer) + (pop-to-buffer image-dired-thumbnail-buffer)))) + +;;;###autoload +(defun diredp-image-dired-tag-files-recursive (&optional ignore-marks-p details) ; Bound to `M-+ C-t t' + "Tag marked files with an `image-dired' tag, including in marked subdirs. +Like `image-dired-tag-files', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-image-dired-required-msg) + (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (let ((tag (read-string "Tags to add (separate tags with a semicolon): "))) + (image-dired-write-tags (mapcar (lambda (x) (cons x tag)) + (diredp-get-files ignore-marks-p nil nil nil nil details))))) + +;;;###autoload +(defun diredp-image-dired-delete-tag-recursive (&optional ignore-marks-p details) ; Bound to `M-+ C-t r' + "Remove `image-dired' tag for marked files, including in marked subdirs. +Like `image-dired-delete-tag', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-image-dired-required-msg) + (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (image-dired-remove-tag (diredp-get-files ignore-marks-p nil nil nil nil details) + (read-string "Tag to remove: "))) + +;;;###autoload +(defun diredp-image-dired-comment-files-recursive (&optional ignore-marks-p details) + ; Bound to `M-+ C-t c' + "Add comment to marked files in dired, including those in marked subdirs. +Like `image-dired-dired-comment-files' but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-image-dired-required-msg) + (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (let ((comment (image-dired-read-comment))) + (image-dired-write-comments (mapcar (lambda (curr-file) (cons curr-file comment)) + (diredp-get-files ignore-marks-p nil nil nil nil details))))) + +(when (> emacs-major-version 22) + + (defun diredp-do-decrypt-recursive (&optional ignore-marks-p details) ; Bound to `M-+ : d' + "Decrypt marked files, including those in marked subdirs. +Like `epa-dired-do-decrypt', but act recursively on subdirs to pick up +the files to decrypt. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (dolist (file (diredp-get-files ignore-marks-p nil nil nil nil details)) + (epa-decrypt-file (expand-file-name file))) + (revert-buffer)) + + (defun diredp-do-verify-recursive (&optional ignore-marks-p details) ; Bound to `M-+ : v' + "Verify marked files, including those in marked subdirs. +Like `epa-dired-do-verify', but act recursively on subdirs to pick up +the files to verify. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (dolist (file (diredp-get-files ignore-marks-p nil nil nil nil details)) + (epa-verify-file (expand-file-name file))) + (revert-buffer)) + + (defun diredp-do-sign-recursive (&optional ignore-marks-p details) ; Bound to `M-+ : s' + "Sign marked files, including those in marked subdirs. +Like `epa-dired-do-sign', but act recursively on subdirs to pick up +the files to sign. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (dolist (file (diredp-get-files ignore-marks-p nil nil nil nil details)) + (epa-sign-file (expand-file-name file) + (epa-select-keys (epg-make-context) "Select keys for signing. +If none are selected, the default secret key is used. ") + (y-or-n-p "Make a detached signature? "))) + (revert-buffer)) + + (defun diredp-do-encrypt-recursive (&optional ignore-marks-p details) ; Bound to `M-+ : e' + "Encrypt marked files, including those in marked subdirs. +Like `epa-dired-do-encrypt', but act recursively on subdirs to pick up +the files to encrypt. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (dolist (file (diredp-get-files ignore-marks-p nil nil nil nil details)) + (epa-encrypt-file (expand-file-name file) + (epa-select-keys (epg-make-context) "Select recipients for encryption. +If none are selected, symmetric encryption is performed. "))) + (revert-buffer))) + +;;;###autoload +(defun diredp-do-bookmark-recursive (&optional ignore-marks-p prefix details) ; Bound to `M-+ M-b' + "Bookmark the marked files, including those in marked subdirs. +Like `diredp-do-bookmark', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + diredp-list-file-attributes))) + (dolist (file (diredp-get-files ignore-marks-p nil nil nil nil details)) + (diredp-bookmark prefix file 'NO-MSG-P))) + +;;;###autoload +(defun diredp-do-bookmark-dirs-recursive (ignore-marks-p &optional details msgp) + "Bookmark this Dired buffer and marked subdirectory Dired buffers, recursively. +Create a Dired bookmark for this directory and for each of its marked +subdirectories. Handle each of the marked subdirectory similarly: +bookmark it and its marked subdirectories, and so on, recursively. +Name each of these Dired bookmarks with the Dired buffer name. + +After creating the Dired bookmarks, create a sequence bookmark, named +`DIRBUF and subdirs', where DIRBUF is the name of the original buffer. +This bookmark represents the whole Dired tree rooted in the directory +where you invoked the command. Jumping to this sequence bookmark +restores all of the Dired buffers making up the tree, by jumping to +each of their bookmarks. + +With a prefix arg, bookmark the marked and unmarked subdirectory Dired +buffers, recursively, that is, ignore markings. + +Note: + +* If there is more than one Dired buffer for a given subdirectory then + only the first such is used. + +* This command creates new bookmarks. It never updates or overwrites + an existing bookmark. + +You need library `Bookmark+' for this command. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-subdirs'." + (interactive (progn (unless (featurep 'bookmark+) + (error "You need library `Bookmark+' for this command")) + (diredp-get-confirmation-recursive 'subdirs) + (list current-prefix-arg diredp-list-file-attributes t))) + (diredp-ensure-mode) + (let ((sdirs (diredp-get-subdirs ignore-marks-p nil details)) + (snames ()) + dbufs) + (when (and msgp sdirs) (message "Checking descendant directories...")) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (let ((bname (bookmark-buffer-name)) + (count 2)) + (while (and (bmkp-get-bookmark-in-alist bname 'NOERROR) (setq bname (format "%s[%d]" bname count)))) + (bookmark-set bname nil nil 'NO-UPDATE-P) ; Inhibit updating displayed list. + (push bname snames))))) + (let ((bname (format "%s and subdirs" (bookmark-buffer-name))) + (count 2)) + (while (and (bmkp-get-bookmark-in-alist bname 'NOERROR) (setq bname (format "%s[%d]" bname count)))) + (bmkp-set-sequence-bookmark bname (nreverse snames) -1 'MSGP)) + (bmkp-refresh/rebuild-menu-list nil))) + +;;;###autoload +(defun diredp-do-bookmark-in-bookmark-file-recursive (bookmark-file ; Bound to `M-+ C-M-B', aka `M-+ C-M-S-b') + &optional prefix ignore-marks-p bfile-bookmarkp details) + "Bookmark files here and below in BOOKMARK-FILE and save BOOKMARK-FILE. +Like `diredp-do-bookmark-in-bookmark-file', but act recursively on +subdirs. The files included are those that are marked in the current +Dired buffer, or all files in the directory if none are marked. +Marked subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp: + * Optional arg BFILE-BOOKMARKP non-nil means create a bookmark-file + bookmark for BOOKMARK-FILE. + * Optional arg DETAILS is passed to `diredp-get-files'." + (interactive + (progn (diredp-get-confirmation-recursive) + (let ((d-r-b-f-args (diredp-read-bookmark-file-args))) + (list (car d-r-b-f-args) + (cadr d-r-b-f-args) + (car (cddr d-r-b-f-args)) + nil + diredp-list-file-attributes)))) + (diredp-do-bookmark-in-bookmark-file bookmark-file prefix nil bfile-bookmarkp + (diredp-get-files ignore-marks-p nil nil nil nil details))) + +;;;###autoload +(defun diredp-set-bookmark-file-bookmark-for-marked-recursive (bookmark-file + &optional prefix ignore-marks-p details) + ; Bound to `M-+ C-M-b' + "Bookmark the marked files and create a bookmark-file bookmark for them. +Like `diredp-set-bookmark-file-bookmark-for-marked', but act +recursively on subdirs. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-bookmark-in-bookmark-file-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) + (let ((d-r-b-f-args (diredp-read-bookmark-file-args))) + (list (car d-r-b-f-args) + (cadr d-r-b-f-args) + (car (cddr d-r-b-f-args)) + diredp-list-file-attributes)))) + (diredp-ensure-bookmark+) + (diredp-do-bookmark-in-bookmark-file-recursive + bookmark-file prefix ignore-marks-p 'CREATE-BOOKMARK-FILE-BOOKMARK details)) + +;;;###autoload +(defun diredp-do-find-marked-files-recursive (&optional arg details) ; Bound to `M-+ F' + "Find marked files simultaneously, including those in marked subdirs. +Like `dired-do-find-marked-files', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With (explicit) numeric prefix ARG >= 0, find the files but do not +display them. + +With numeric prefix ARG <= 0, ignore all marks - include all files in +this Dired buffer and all subdirs, recursively. + +Note that prefix-argument behavior is different for this command than +for `dired-do-find-marked-files'. In particular, a negative numeric +prefix arg does not cause the files to be shown in separate frames. +Only non-nil `pop-up-frames' (or equivalent configuration) causes +the files to be shown in separate frames. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (let ((narg (prefix-numeric-value arg))) + (dired-simultaneous-find-file (diredp-get-files (<= narg 0) nil nil nil nil details) + (and arg (>= narg 0) narg)))) + +(when (fboundp 'dired-do-isearch-regexp) ; Emacs 23+ + + (defun diredp-do-isearch-recursive (&optional ignore-marks-p details) ; Bound to `M-+ M-s a C-s' + "Isearch the marked files, including those in marked subdirs. +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (multi-isearch-files (diredp-get-files ignore-marks-p nil nil nil nil details))) + + (defun diredp-do-isearch-regexp-recursive (&optional ignore-marks-p details) ; `M-+ M-s a C-M-s' + "Regexp-Isearch the marked files, including those in marked subdirs. +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (multi-isearch-files-regexp (diredp-get-files ignore-marks-p nil nil nil nil details)))) + +(defun diredp-do-search-recursive (regexp &optional ignore-marks-p details) ; Bound to `M-+ A' + "Regexp-search the marked files, including those in marked subdirs. +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +Stops when a match is found. +To continue searching for the next match, use `\\[tags-loop-continue]'. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list (read-string "Search marked files (regexp): ") + current-prefix-arg + diredp-list-file-attributes))) + (tags-search regexp '(diredp-get-files ignore-marks-p nil nil nil nil details))) + +;;;###autoload +(defun diredp-do-query-replace-regexp-recursive (from to &optional arg details) + ; Bound to `M-+ Q' + "Do `query-replace-regexp' on marked files, including in marked subdirs. +Query-replace FROM with TO. + +Like `dired-do-query-replace', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With an (explicit) numeric prefix argument: + +* >= 0 means ignore all marks - include ALL files in this Dired buffer + and all subdirs, recursively. + +* <= 0 means replace only word-delimited matches. + +If you exit (`\\[keyboard-quit]', `RET' or `q'), you can resume the query replacement +using `\\[tags-loop-continue]'. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (let ((common (query-replace-read-args "Query replace regexp in marked files" t t))) + (list (nth 0 common) + (nth 1 common) + current-prefix-arg + diredp-list-file-attributes)))) + (let* ((narg (and arg (prefix-numeric-value arg))) + (delimited (and narg (<= narg 0))) + (ignore-marks-p (and narg (>= narg 0))) + (files (diredp-get-files ignore-marks-p nil nil nil nil details)) + (fit-frame-min-width 30) + (fit-frame-min-height 15)) + (dolist (file files) + (let ((buffer (get-file-buffer file))) + (when (and buffer (with-current-buffer buffer buffer-read-only)) + (error "File `%s' is visited read-only" file)))) + (tags-query-replace from to delimited `',files))) + +;;;###autoload +(defun diredp-do-grep-recursive (command-args &optional details) ; Bound to `M+ C-M-G' + "Run `grep' on marked files, including those in marked subdirs. +Like `diredp-do-grep', but act recursively on subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (unless (if (< emacs-major-version 22) + grep-command + (and grep-command (or (not grep-use-null-device) (eq grep-use-null-device t)))) + (grep-compute-defaults)) + (list (diredp-do-grep-1 + (diredp-get-files current-prefix-arg nil nil nil nil diredp-list-file-attributes))))) + (grep command-args)) + +;;;###autoload +(defun diredp-marked-recursive (dirname &optional ignore-marks-p details) ; Not bound to a key + "Open Dired on marked files, including those in marked subdirs. +Like `diredp-marked', but act recursively on subdirs. + +See `diredp-do-find-marked-files-recursive' for a description of the +files included. In particular, if no files are marked here or in a +marked subdir, then all files in the directory are included. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, DIRNAME here must be a string, not a cons. It +is used as the name of the new Dired buffer. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list nil current-prefix-arg diredp-list-file-attributes))) + (dired (cons (or dirname (generate-new-buffer-name (buffer-name))) + (diredp-get-files ignore-marks-p nil nil nil nil details)))) + +;;;###autoload +(defun diredp-marked-recursive-other-window (dirname &optional ignore-marks-p details) ; Bound to `M-+ C-M-*' + "Same as `diredp-marked-recursive', but uses a different window. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list nil current-prefix-arg diredp-list-file-attributes))) + (dired-other-window + (cons (or dirname (generate-new-buffer-name (buffer-name))) + (diredp-get-files ignore-marks-p nil nil nil nil details)))) + +;;;###autoload +(defun diredp-list-marked-recursive (&optional ignore-marks-p predicate details) ; Bound to `M-+ C-M-l' + "List the files marked here and in marked subdirs, recursively. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, all marks are ignored: all files in this Dired +buffer and all descendant directories are included. + +You can use `RET' or `mouse-2' to visit any of the files. +If `tooltip-mode' is on then moving the mouse over image-file names +shows image previews. + +When called from Lisp: + Non-nil optional arg IGNORE-MARKS-P means ignore marks. + Non-nil optional arg PREDICATE is a file-name predicate. List only + the files for which it returns non-nil. + Non-nil optional arg DETAILS is passed to `diredp-list-files'." + (interactive ; No need for `diredp-get-confirmation-recursive' here. + (progn (diredp-ensure-mode) (list current-prefix-arg nil diredp-list-file-attributes))) + (let ((files (diredp-get-files ignore-marks-p predicate))) (diredp-list-files files nil nil nil details))) + +;;;###autoload +(defun diredp-flag-auto-save-files-recursive (&optional arg details) ; `M-+ #' + "Flag all auto-save files for deletion, including in marked subdirs. +A non-negative prefix arg means to unmark (unflag) them instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, flag all in this directory and all +descendant directories. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-mark-recursive-1'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (let ((dired-marker-char dired-del-marker)) + (diredp-mark-recursive-1 arg "auto-save files" "auto-save file" '(diredp-looking-at-p "^.* #.+#$") details))) + +(when (fboundp 'char-displayable-p) ; Emacs 22+ + + (defun diredp-change-marks-recursive (old new &optional arg predicate details) ; `M-+ * c' + "Change all OLD marks to NEW marks, including those in marked subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +* A non-positive prefix arg means ignore subdir markings and act + instead on ALL subdirs. + +* A non-negative prefix arg means do not change marks on subdirs + themselves. + +Note: If there is more than one Dired buffer for a given subdirectory +then only the first such is used. + +When called from Lisp: + Non-nil arg PREDICATE is a file-name predicate. Act on only the + files for which it returns non-nil. + DETAILS is passed to `diredp-get-subdirs'." + (interactive + (progn (diredp-get-confirmation-recursive) + (let* ((cursor-in-echo-area t) + (old (progn (message "Change (old mark): ") (read-char))) + (new (progn (message "Change `%c' marks to (new mark): " old) (read-char)))) + (list old new current-prefix-arg nil diredp-list-file-attributes)))) + (let* ((numarg (and arg (prefix-numeric-value arg))) + (nosubs (natnump numarg)) + (ignore-marks (and numarg (<= numarg 0))) + (dired-marker-char new) + (sdirs (diredp-get-subdirs ignore-marks predicate details)) + (old-strg (format "\n%c" old)) + (count 0) + dbufs) + (unless (char-displayable-p old) (error "Not a displayable character: `%c'" old)) + (unless (char-displayable-p new) (error "Not a displayable character: `%c'" new)) + (message "Changing mark `%c' to `%c'..." old new) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (let ((inhibit-read-only t) + (file nil)) + (save-excursion + (goto-char (point-min)) + (while (search-forward old-strg nil t) + (save-match-data (setq file (dired-get-filename 'no-dir t))) + ;; Do nothing if changing from UNmarked and not on a file or dir name. + (unless (and (= old ? ) (not file)) + ;; Do nothing if marked subdir and not changing subdir marks. + (unless (and nosubs file (file-directory-p file)) + (subst-char-in-region (match-beginning 0) (match-end 0) old new) + (setq count (1+ count)))))))))) + (message "%d mark%s changed from `%c' to `%c'" count (dired-plural-s count) old new))) + + (defun diredp-unmark-all-marks-recursive (&optional arg details) ; `M-+ U' + "Remove ALL marks everywhere, including in marked subdirs. +A prefix arg is as for `diredp-unmark-all-files-recursive'. +Note that a negative prefix arg (e.g. `C--') removes all marks from +this Dired buffer and then does the same recursively for each of its +subdirs. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-unmark-all-files-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (diredp-unmark-all-files-recursive ?\r arg details)) + + (defun diredp-unmark-all-files-recursive (mark &optional arg predicate details) ; `M-+ M-DEL' + "Remove a given mark (or ALL) everywhere, including in marked subdirs. +You are prompted for the mark character to remove. If you hit `RET' +instead then ALL mark characters are removed. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +* A non-positive prefix arg means ignore subdir markings and act + instead on ALL subdirs. + +* A non-negative prefix arg means do not change marks on subdirs + themselves. + +Note: If there is more than one Dired buffer for a given subdirectory +then only the first such is used. + +When called from Lisp: + Non-nil arg PREDICATE is a file-name predicate. Act on only the + files for which it returns non-nil. + DETAILS is passed to `diredp-get-subdirs'." + (interactive + (progn (diredp-get-confirmation-recursive) + (let* ((cursor-in-echo-area t) + (mrk (progn (message "Remove marks (RET means all): ") (read-char)))) + (list mrk current-prefix-arg nil diredp-list-file-attributes)))) + (let* ((numarg (and arg (prefix-numeric-value arg))) + (nosubs (natnump numarg)) + (ignore-marks (and numarg (<= numarg 0))) + (dired-marker-char ?\ ) ; Unmark + (sdirs (diredp-get-subdirs ignore-marks predicate details)) + (mrk-strg (format "\n%c" mark)) + (count 0) + dbufs) + (unless (char-displayable-p mark) (error "Not a displayable character: `%c'" mark)) + (if (eq mark ?\r) + (message "Unmarking ALL marks here and below...") + (message "Unmarking mark `%c' here and below..." mark)) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (let ((inhibit-read-only t) + (file nil)) + (save-excursion + (goto-char (point-min)) + (while (if (eq mark ?\r) + (re-search-forward dired-re-mark nil t) + (search-forward mrk-strg nil t)) + (save-match-data (setq file (dired-get-filename 'no-dir t))) + ;; Do nothing if marked subdir and not changing subdir marks. + (unless (and nosubs file (file-directory-p file)) + (subst-char-in-region (match-beginning 0) (match-end 0) (preceding-char) ?\ )) + (setq count (1+ count)))))))) + (message "%d mark%s UNmarked" count (dired-plural-s count)))) + + ) + +(when (and (memq system-type '(windows-nt ms-dos)) (fboundp 'w32-browser)) + + (defun diredp-multiple-w32-browser-recursive (&optional ignore-marks-p details) + "Run Windows apps for with marked files, including those in marked subdirs. +Like `dired-multiple-w32-browser', but act recursively on subdirs. + +See `diredp-do-find-marked-files-recursive' for a description of the +files included. In particular, if no files are marked here or in a +marked subdir, then all files in the directory are included. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list current-prefix-arg diredp-list-file-attributes))) + (let ((files (diredp-get-files ignore-marks-p nil nil nil nil details))) + (while files + (w32-browser (car files)) + (sleep-for w32-browser-wait-time) + (setq files (cdr files))))) + + ) + +;;;###autoload +(defun diredp-copy-filename-as-kill-recursive (&optional arg details) ; Bound to `M-+ M-w' + "Copy names of marked files here and in marked subdirs, to `kill-ring'. +The names are separated by a space. + +Like `dired-copy-filename-as-kill', but act recursively on subdirs. +\(Do not copy subdir names themselves.) + +With no prefix arg, use relative file names. +With a zero prefix arg, use absolute file names. +With a plain prefix arg (`C-u'), use names relative to the current +Dired directory. (This might contain slashes if in a subdirectory.) + +If on a subdir headerline, use absolute subdir name instead - prefix +arg and marked files are ignored in this case. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +The names are copied to the kill ring and to variable +`diredp-last-copied-filenames'. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive ; No need for `diredp-get-confirmation-recursive' here. + (progn (diredp-ensure-mode) (list current-prefix-arg diredp-list-file-attributes))) + (let* ((files (mapcar (cond ((zerop (prefix-numeric-value arg)) #'identity) + ((consp arg) (lambda (fn) (concat (dired-current-directory t) + (file-name-nondirectory fn)))) + (t (lambda (fn) (file-name-nondirectory fn)))) + (diredp-get-files nil nil nil nil nil details))) + (string (mapconcat #'identity files " "))) + (unless (string= "" string) + (if (eq last-command 'kill-region) (kill-append string nil) (kill-new string)) + (setq diredp-last-copied-filenames (car kill-ring-yank-pointer))) + (message "%s" string))) + +;;;###autoload +(defun diredp-copy-abs-filenames-as-kill-recursive (&optional ignore-marks-p details) ; Not bound. + "Copy absolute names of files marked here and in marked subdirs, recursively. +The names are copied to the kill ring and to variable +`dired-copy-filename-as-kill'. + +The files whose names are copied are those that are marked in the +current Dired buffer, or all files in the directory if none are +marked. Marked subdirectories are handled recursively in the same +way. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-copy-filename-as-kill-recursive'." + (interactive ; No need for `diredp-get-confirmation-recursive' here. + (progn (diredp-ensure-mode) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-copy-filename-as-kill-recursive 0 details) + (setq diredp-last-copied-filenames (car kill-ring-yank-pointer))) + +;;;###autoload +(defun diredp-mark-files-regexp-recursive (regexp + &optional marker-char ignore-marks-p details) ; Bound to `M-+ % m' + "Mark all files matching REGEXP, including those in marked subdirs. +Like `dired-mark-files-regexp' but act recursively on marked subdirs. + +The file names to be matched by this command are always absolute - +they include the full directory. Note that this does NOT correspond +to the default behavior for `dired-mark-files-regexp'. The other +matching possibilities offered by `dired-mark-files-regexp' are not +available for this command. + +Directories `.' and `..' are never marked. + +A non-negative prefix arg means to UNmark the files instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all matching files in this +directory and all descendant directories. + +REGEXP is an Emacs regexp, not a shell wildcard. Thus, use `\\.o$' for +object files--just `.o' will mark more than you might think. + +REGEXP is added to `regexp-search-ring', for regexp search. + +Note: If there is more than one Dired buffer for a given subdirectory +then only the first such is used. + +When called from Lisp, DETAILS is passed to `diredp-get-subdirs'." + (interactive (let* ((numarg (and current-prefix-arg (prefix-numeric-value current-prefix-arg))) + (unmark (and numarg (>= numarg 0))) + (ignorep (and numarg (<= numarg 0)))) + (list (diredp-read-regexp (concat (if unmark "UNmark" "Mark") " files (regexp): ")) + (and unmark ?\040) + ignorep + diredp-list-file-attributes))) + (add-to-list 'regexp-search-ring regexp) ; Add REGEXP to `regexp-search-ring'. + (let ((dired-marker-char (or marker-char dired-marker-char)) + (sdirs (diredp-get-subdirs ignore-marks-p nil details)) + (matched 0) + (changed 0) + dbufs chg.mtch) + (message "%s files..." (if (eq ?\040 dired-marker-char) "UNmarking" "Marking")) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (setq chg.mtch (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) + (not (eolp)) ; Empty line + (let ((fn (dired-get-filename nil 'NO-ERROR))) + (and fn (diredp-string-match-p regexp fn)))) + "file") + changed (+ changed (or (car chg.mtch) 0)) + matched (+ matched (or (cdr chg.mtch) 0)))))) + (message "%s file%s%s%s newly %s" + matched + (dired-plural-s matched) + (if (not (= matched changed)) " matched, " "") + (if (not (= matched changed)) changed "") + (if (eq ?\040 dired-marker-char) "unmarked" "marked")))) + +;;;###autoload +(defun diredp-mark-files-containing-regexp-recursive (regexp + &optional marker-char ignore-marks-p details) ; `M-+ % g' + "Mark files with contents containing a REGEXP match, including in marked subdirs. +Like `dired-mark-files-containing-regexp' but act recursively on +marked subdirs. + +A non-negative prefix arg means to UNmark the files instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all matching files in this +directory and all descendant directories. + +REGEXP is added to `regexp-search-ring', for regexp search. + +Note: If there is more than one Dired buffer for a given subdirectory +then only the first such is used. + +If a file is visited in a buffer and `dired-always-read-filesystem' is +nil, this looks in the buffer without revisiting the file, so the +results might be inconsistent with the file on disk if its contents +have changed since it was last visited. + +When called from Lisp, DETAILS is passed to `diredp-get-subdirs'." + + (interactive (let* ((numarg (and current-prefix-arg (prefix-numeric-value current-prefix-arg))) + (unmark (and numarg (>= numarg 0))) + (ignorep (and numarg (<= numarg 0)))) + (list (diredp-read-regexp (concat (if unmark "UNmark" "Mark") " files containing (regexp): ")) + (and unmark ?\040) + ignorep + diredp-list-file-attributes))) + (add-to-list 'regexp-search-ring regexp) ; Add REGEXP to `regexp-search-ring'. + (let ((dired-marker-char (or marker-char dired-marker-char)) + (sdirs (diredp-get-subdirs ignore-marks-p nil details)) + (matched 0) + (changed 0) + dbufs chg.mtch) + (message "%s files..." (if (eq ?\040 dired-marker-char) "UNmarking" "Marking")) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (setq chg.mtch + (diredp-mark-if + (and (not (diredp-looking-at-p dired-re-dot)) + (not (eolp)) + (let ((fname (dired-get-filename nil t))) + + (and fname + (file-readable-p fname) + (not (file-directory-p fname)) + (let ((prebuf (get-file-buffer fname))) + (message "Checking %s" fname) + ;; For now, do it inside Emacs. Grep might be better if there are lots of files. + (if (and prebuf (or (not (boundp 'dired-always-read-filesystem)) + (not dired-always-read-filesystem))) ; Emacs 26+ + (with-current-buffer prebuf + (save-excursion (goto-char (point-min)) (re-search-forward regexp nil t))) + (with-temp-buffer + (insert-file-contents fname) + (goto-char (point-min)) + (re-search-forward regexp nil t))))))) + "file") + changed (+ changed (or (car chg.mtch) 0)) + matched (+ matched (or (cdr chg.mtch) 0)))))) + (message "%s file%s%s%s newly %s" + matched + (dired-plural-s matched) + (if (not (= matched changed)) " matched, " "") + (if (not (= matched changed)) changed "") + (if (eq ?\040 dired-marker-char) "unmarked" "marked")))) + +(defun diredp-mark-extension-recursive (extension &optional arg details) ; Bound to `M-+ * .' + "Mark all files with a certain EXTENSION, including in marked subdirs. +A `.' is not automatically prepended to the string entered. + +This is like `diredp-mark/unmark-extension', but this acts recursively +on marked subdirs, and a non-positive prefix arg acts differently. + +A non-negative prefix arg means to unmark them instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all in this directory and all +descendant directories. + +Non-interactively, EXTENSION is the extension (a string). It can also +be a list of extension strings. +Optional argument ARG is the prefix arg. + +When called from Lisp, DETAILS is passed to `diredp-mark-files-regexp-recursive'." + (interactive (let* ((numarg (and current-prefix-arg (prefix-numeric-value current-prefix-arg))) + (unmark (and numarg (>= numarg 0)))) + (list (diredp-read-regexp (concat (if unmark "UNmark" "Mark") " extension: ")) + current-prefix-arg + diredp-list-file-attributes))) + (let* ((numarg (and arg (prefix-numeric-value arg))) + (unmark (and numarg (>= numarg 0))) + (ignorep (and numarg (<= numarg 0)))) + (or (listp extension) (setq extension (list extension))) + (diredp-mark-files-regexp-recursive (concat ".+[.]\\(" + (mapconcat #'regexp-quote extension "\\|") + "\\)$") + (if unmark ?\040 dired-marker-char) + ignorep + details))) + +;; FIXME: Factor out code that is common with `dired-mark-sexp'. +;; +(when (fboundp 'minibuffer-with-setup-hook) ; Emacs 22+ + + (defun diredp-mark-sexp-recursive (predicate &optional arg details) ; Bound to `M-+ M-(', `M-+ * (' + "Mark files here and below for which PREDICATE returns non-nil. +Like `diredp-mark-sexp', but act recursively on subdirs. + +A non-negative prefix arg means to unmark those files instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all in this directory and all +descendant directories. + +PREDICATE is a lisp sexp that can refer to the following symbols as +variables: + + `mode' [string] file permission bits, e.g. \"-rw-r--r--\" + `nlink' [integer] number of links to file + `size' [integer] file size in bytes + `uid' [string] owner + `gid' [string] group (If the gid is not displayed by `ls', + this will still be set (to the same as uid)) + `time' [string] the time that `ls' displays, e.g. \"Feb 12 14:17\" + `name' [string] the name of the file + `sym' [string] if file is a symbolic link, the linked-to name, + else \"\" + `inode' [integer] the inode of the file (only for `ls -i' output) + `blks' [integer] the size of the file for `ls -s' output + (ususally in blocks or, with `-k', in Kbytes) +Examples: + Mark zero-length files: `(equal 0 size)' + Mark files last modified on Feb 2: `(string-match \"Feb 2\" time)' + Mark uncompiled Emacs Lisp files (`.el' file without a `.elc' file): + First, Dired just the source files: `dired *.el'. + Then, use \\[diredp-mark-sexp-recursive] with this sexp: + (not (file-exists-p (concat name \"c\"))) + +There's an ambiguity when a single integer not followed by a unit +prefix precedes the file mode: It is then parsed as inode number +and not as block size (this always works for GNU coreutils ls). + +Another limitation is that the uid field is needed for the +function to work correctly. In particular, the field is not +present for some values of `ls-lisp-emulation'. + +This function operates only on the Dired buffer content. It does not +refer at all to the underlying file system. Contrast this with +`find-dired', which might be preferable for the task at hand. + +When called from Lisp, DETAILS is passed to `diredp-get-subdirs'." + ;; Using `sym' = "", instead of nil, for non-linked files avoids the trap of + ;; (string-match "foo" sym) into which a user would soon fall. + ;; Use `equal' instead of `=' in the example, as it works on integers and strings. + ;; (interactive "xMark if (vars: inode,blks,mode,nlink,uid,gid,size,time,name,sym): \nP") + + (interactive + (let* ((numarg (and current-prefix-arg (prefix-numeric-value current-prefix-arg))) + (unmark (and numarg (>= numarg 0)))) + (diredp-get-confirmation-recursive) + (list (diredp-read-expression (format "%s if (Lisp expr): " (if current-prefix-arg "UNmark" "Mark"))) + current-prefix-arg + diredp-list-file-attributes))) + (message "%s" predicate) + (let* ((numarg (and arg (prefix-numeric-value arg))) + (unmark (and numarg (>= numarg 0))) + (ignorep (and numarg (<= numarg 0))) + (dired-marker-char (if unmark ?\040 dired-marker-char)) + (inode nil) + (blks ()) + (matched 0) + (changed 0) + dbufs chg.mtch mode nlink uid gid size time name sym) + (dolist (dir (cons default-directory (diredp-get-subdirs ignorep nil details))) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (setq chg.mtch + (diredp-mark-if + (save-excursion + (and + ;; Sets vars INODE BLKS MODE NLINK UID GID SIZE TIME NAME and SYM + ;; according to current file line. Returns `t' for success, nil if + ;; there is no file line. Upon success, these vars are set, to either + ;; nil or the appropriate value, so they need not be initialized. + ;; Moves point within the current line. + (dired-move-to-filename) + (let ((mode-len 10) ; Length of `mode' string. + ;; As in `dired.el', but with subexpressions \1=inode, \2=blks: + ;; GNU `ls -hs' suffixes the block count with a unit and prints it as a float + ;; FreeBSD does neither. + ;; $$$$$$ (dired-re-inode-size "\\s *\\([0-9]*\\)\\s *\\([0-9]*\\) ?") + (dired-re-inode-size (if (> emacs-major-version 24) + "\\=\\s *\\([0-9]+\\s +\\)?\ +\\(?:\\([0-9]+\\(?:\\.[0-9]*\\)?[BkKMGTPEZY]?\\)? ?\\)" + "\\s *\\([0-9]*\\)\\s *\\([0-9]*\\) ?")) + pos) + (beginning-of-line) + (forward-char 2) + (search-forward-regexp dired-re-inode-size nil t) + ;; `INODE', `BLKS', `MODE' + ;; XXX Might be a size not followed by a unit prefix. + ;; Could set `blks' to `inode' if it were otherwise nil, with similar reasoning + ;; as for setting `gid' to `uid', but it would be even more whimsical. + (setq inode (and (match-string 1) (string-to-number (match-string 1))) + blks (and (match-string 2) (if (fboundp 'dired-x--string-to-number) ; Emacs 25+ + (dired-x--string-to-number (match-string 2)) + (string-to-number (match-string 2)))) + mode (buffer-substring (point) (+ mode-len (point)))) + (forward-char mode-len) + ;; Skip any extended attributes marker ("." or "+"). + (unless (eq (char-after) ?\ ) (forward-char 1)) + (setq nlink (read (current-buffer))) ; `NLINK' + + ;; `UID' + ;; Another issue is that GNU `ls -n' right-justifies numerical UIDs and GIDs, + ;; while FreeBSD left-justifies them, so do not rely on a specific whitespace + ;; layout. Both of them right-justify all other numbers, though. + ;; XXX Return a number if the `uid' or `gid' seems to be numerical? + ;; $$$$$$ (setq uid (buffer-substring (+ (point) 1) (progn (forward-word 1) (point)))) + (setq uid (buffer-substring (progn (skip-chars-forward " \t") (point)) + (progn (skip-chars-forward "^ \t") (point)))) + (cond ((> emacs-major-version 24) + (dired-move-to-filename) + (save-excursion + (setq time ; `TIME' + ;; The regexp below tries to match from the last digit of the size + ;; field through a space after the date. Also, dates may have + ;; different formats depending on file age, so the date column need + ;; not be aligned to the right. + (buffer-substring + (save-excursion (skip-chars-backward " \t") (point)) + (progn (re-search-backward directory-listing-before-filename-regexp) + (skip-chars-forward "^ \t") + (1+ (point)))) + + size ; `SIZE' + (dired-x--string-to-number + ;; We know that there's some kind of number before point because + ;; the regexp search above succeeded. Not worth doing an extra + ;; check for leading garbage. + (buffer-substring (point) (progn (skip-chars-backward "^ \t") (point)))) + ;; If no `gid' is displayed, `gid' will be set to `uid' but user + ;; will then not reference it anyway in PREDICATE. + + gid ; `GID' + (buffer-substring (progn (skip-chars-backward " \t") (point)) + (progn (skip-chars-backward "^ \t") (point))))) + ;; `NAME', `SYM' + (setq name (buffer-substring (point) + (or (dired-move-to-end-of-filename t) (point))) + sym (if (diredp-looking-at-p " -> ") + (buffer-substring (progn (forward-char 4) (point)) + (line-end-position)) + ""))) + (t + (re-search-forward + (if (< emacs-major-version 20) + "\\(Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|Sep\\|Oct\\|Nov\\|Dec\\)" + dired-move-to-filename-regexp)) + (goto-char (match-beginning 1)) + (forward-char -1) + (setq size ; `SIZE' + (string-to-number (buffer-substring (save-excursion (backward-word 1) + (setq pos (point))) + (point)))) + (goto-char pos) + (backward-word 1) + ;; `GID', `TIME', `NAME', `SYM' + ;; if no `gid' is displayed, `gid' will be set to `uid' but user will then + ;; not reference it anyway in PREDICATE. + (setq gid (buffer-substring (save-excursion (forward-word 1) (point)) (point)) + time (buffer-substring (match-beginning 1) (1- (dired-move-to-filename))) + name (buffer-substring (point) (or (dired-move-to-end-of-filename t) + (point))) + sym (if (diredp-looking-at-p " -> ") + (buffer-substring (progn (forward-char 4) (point)) + (line-end-position)) + ""))))) + ;; Vanilla Emacs uses `lexical-binding' = t, and it passes bindings to `eval' + ;; as a second arg. We use `lexical-binding' = nil, and anyway there should + ;; be no need to pass the bindings. + (eval predicate))) + (format "'%s file" predicate))) + (setq changed (+ changed (or (car chg.mtch) 0)) + matched (+ matched (or (cdr chg.mtch) 0)))))) + (message "%s file%s%s%s newly %s" matched (dired-plural-s matched) + (if (not (= matched changed)) " matched, " "") + (if (not (= matched changed)) changed "") + (if (eq ?\040 dired-marker-char) "unmarked" "marked")))) + + (if (fboundp 'read--expression) ; Emacs 24.4+ + (defalias 'diredp-read-expression 'read--expression) + (defun diredp-read-expression (prompt &optional initial-contents) + (let ((minibuffer-completing-symbol t)) + (minibuffer-with-setup-hook + (lambda () ; Vanilla Emacs FIXME: call `emacs-lisp-mode'? + (add-function :before-until (local 'eldoc-documentation-function) + #'elisp-eldoc-documentation-function) + (eldoc-mode 1) + (add-hook 'completion-at-point-functions #'elisp-completion-at-point nil t) + (run-hooks 'eval-expression-minibuffer-setup-hook)) + (read-from-minibuffer + prompt initial-contents (if (boundp 'pp-read-expression-map) + pp-read-expression-map + read-expression-map) + t 'read-expression-history))))) + + ) + +;;;###autoload +(defun diredp-mark-autofiles-recursive (&optional arg details) ; Bound to `M-+ * B' + "Mark all autofiles, including in marked subdirs. +Autofiles are files that have an autofile bookmark. +A non-negative prefix arg means to unmark them instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all in this directory and all +descendant directories. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-mark-recursive-1'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-recursive-1 arg "autofiles" "autofile" + '(and (not (diredp-looking-at-p dired-re-dot)) (not (eolp)) + (let ((fname (dired-get-filename nil t))) + (and fname (bmkp-get-autofile-bookmark fname)))) + details)) + +;;;###autoload +(defun diredp-mark-executables-recursive (&optional arg details) ; Bound to `M-+ * *' + "Mark all executable files, including in marked subdirs. +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +A non-negative prefix arg means to unmark them instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all in this directory and all +descendant directories. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-mark-recursive-1'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (diredp-mark-recursive-1 arg "executable files" "executable file" '(diredp-looking-at-p dired-re-exe) details)) + +;;;###autoload +(defun diredp-mark-directories-recursive (&optional arg details) ; Bound to `M-+ * /' + "Mark all directories except `.' and `..', including in marked subdirs. +The directories included are those that are marked in the current +Dired buffer, or all subdirs in the directory if none are marked. +Marked subdirectories are handled recursively in the same way. + +A non-negative prefix arg means to unmark them instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all in this directory and all +descendant directories. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-mark-recursive-1'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (diredp-mark-recursive-1 arg "directories" "directory" '(and (diredp-looking-at-p dired-re-dir) + (not (diredp-looking-at-p dired-re-dot))) + details)) +;;;###autoload +(defun diredp-mark-symlinks-recursive (&optional arg details) ; Bound to `M-+ * @' + "Mark all symbolic links, including in marked subdirs. +The symlinks included are those that are marked in the current Dired +buffer, or all symlinks in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +A non-negative prefix arg means to unmark them instead. + +A non-positive prefix arg means to ignore subdir markings and act +instead on ALL subdirs. That is, mark all in this directory and all +descendant directories. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-subdirs'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (diredp-mark-recursive-1 arg "symlinks" "symbolic link" '(diredp-looking-at-p dired-re-sym) details)) + +(defun diredp-mark-recursive-1 (arg plural singular predicate-sexp details) + "Helper for `diredp-mark-*-recursive' commands." + (let* ((numarg (and arg (prefix-numeric-value arg))) + (unmark (and numarg (>= numarg 0))) + (ignorep (and numarg (<= numarg 0))) + (dired-marker-char (if unmark ?\040 dired-marker-char)) + (sdirs (diredp-get-subdirs ignorep nil details)) + (changed 0) + (matched 0) + dbufs chg.mtch) + (message "%s %s..." (if (eq ?\040 dired-marker-char) "UNmarking" "Marking") plural) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) + (setq chg.mtch (diredp-mark-if (eval predicate-sexp) singular) + changed (+ changed (or (car chg.mtch) 0)) + matched (+ matched (or (cdr chg.mtch) 0)))))) + (message "%s %s%s%s newly %s" + matched + (if (= 1 matched) singular plural) + (if (not (= matched changed)) " matched, " "") + (if (not (= matched changed)) changed "") + (if (eq ?\040 dired-marker-char) "unmarked" "marked")))) + +;;;###autoload +(defun diredp-capitalize-recursive (&optional ignore-marks-p details) ; Bound to `M-+ % c' + "Rename marked files, including in marked subdirs, by capitalizing them. +Like `diredp-capitalize', but act recursively on subdirs. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-create-files-non-directory-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-create-files-non-directory-recursive + #'dired-rename-file #'capitalize "Rename by capitalizing:" ignore-marks-p details)) + +;;;###autoload +(defun diredp-upcase-recursive (&optional ignore-marks-p details) ; Bound to `M-+ % u' + "Rename marked files, including in marked subdirs, making them uppercase. +Like `dired-upcase', but act recursively on subdirs. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-create-files-non-directory-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-create-files-non-directory-recursive + #'dired-rename-file #'upcase "Rename to uppercase:" ignore-marks-p details)) + +;;;###autoload +(defun diredp-downcase-recursive (&optional ignore-marks-p details) ; Bound to `M-+ % l' + "Rename marked files, including in marked subdirs, making them lowercase. +Like `dired-downcase', but act recursively on subdirs. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-create-files-non-directory-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-create-files-non-directory-recursive + #'dired-rename-file #'downcase "Rename to lowercase:" ignore-marks-p details)) + +;;;###autoload +(defun diredp-do-apply-function-recursive (function &optional arg details) ; Bound to `M-+ @' + "Apply FUNCTION to the marked files. +Like `diredp-do-apply-function' but act recursively on subdirs and do +no result or error logging or echoing. + +The files acted on are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +With a plain prefix ARG (`C-u'), visit each file and invoke FUNCTION + with no arguments. +Otherwise, apply FUNCTION to each file name. + +Any other prefix arg behaves according to the ARG argument of +`dired-get-marked-files'. In particular, `C-u C-u' operates on all +files in the Dired buffer. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-get-confirmation-recursive) + (list (read (completing-read "Function: " obarray 'functionp nil nil + (and (boundp 'function-name-history) 'function-name-history))) + current-prefix-arg + diredp-list-file-attributes))) + (if (and (consp arg) (< (car arg) 16)) + (dolist (file (diredp-get-files)) (with-current-buffer (find-file-noselect file) (funcall function))) + (dolist (file (diredp-get-files arg nil nil nil nil details)) (funcall function file)))) + +;;;###autoload +(defun diredp-do-delete-recursive (arg &optional details) ; Bound to `M-+ D' + "Delete marked (not flagged) files, including in marked subdirs. +Like `dired-do-delete' but act recursively on subdirs. + +The files to be deleted are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files' and `diredp-get-subdirs'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (unless arg + (ding) + (message "NOTE: Deletion of files marked `%c' (not those flagged `%c')." + dired-marker-char dired-del-marker)) + (let* ((files (diredp-get-files nil nil nil nil 'ONLY-MARKED-P details)) + (count (length files)) + (trashing (and (boundp 'delete-by-moving-to-trash) delete-by-moving-to-trash)) + (succ 0)) + (if (dired-mark-pop-up + " *Deletions*" 'delete files dired-deletion-confirmer + (format "%s %s " (if trashing "Trash" "Delete") (dired-mark-prompt arg files))) + (let ((progress-reporter (and (fboundp 'make-progress-reporter) + (make-progress-reporter (if trashing "Trashing..." "Deleting...") + succ + count))) + (failures ())) + (unless progress-reporter (message "Deleting...")) + (dolist (file files) + (condition-case err + (progn (if (fboundp 'dired-delete-file) ; Emacs 22+ + (dired-delete-file file dired-recursive-deletes trashing) + ;; This test is equivalent to (and (file-directory-p file) (not (file-symlink-p file))) + ;; but more efficient. + (if (eq t (car (file-attributes file))) (delete-directory file) (delete-file file))) + (setq succ (1+ succ)) + (when (fboundp 'progress-reporter-update) + (progress-reporter-update progress-reporter succ))) + (error (dired-log "%s\n" err) ; Catch errors from failed deletions. + (setq failures (cons file failures)))) + (dired-clean-up-after-deletion file)) + (if failures + (dired-log-summary (format "%d of %d deletion%s failed" + (length failures) count (dired-plural-s count)) + failures) + (if (fboundp 'progress-reporter-done) + (progress-reporter-done progress-reporter) + (message "Deleting...done"))) + (let ((sdirs (diredp-get-subdirs nil nil details)) + dbufs) + (dolist (dir (cons default-directory sdirs)) + (when (setq dbufs (dired-buffers-for-dir (expand-file-name dir))) ; Dirs with Dired buffers only. + (with-current-buffer (car dbufs) (dired-revert)))))) + (message "OK. NO deletions performed")))) + +;;;###autoload +(defun diredp-do-move-recursive (&optional ignore-marks-p details) ; Bound to `M-+ R' + "Move marked files, including in marked subdirs, to a given directory. +Like `dired-do-rename', but act recursively on subdirs to pick up the +files to move. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +This means move the marked files of marked subdirs and their marked +subdirs, etc. It does not mean move or rename the subdirs themselves +recursively. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +Renames any buffers that are visiting the files. + +The default suggested for the target directory depends on the value of +`dired-dwim-target', which see." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-do-create-files-recursive #'dired-rename-file "Move" ignore-marks-p details)) + +;;;###autoload +(defun diredp-do-copy-recursive (&optional ignore-marks-p details) ; Bound to `M-+ C' + "Copy marked files, including in marked subdirs, to a given directory. +Like `dired-do-copy', but act recursively on subdirs to pick up the +files to copy. + +The files included are those that are marked in the current Dired +buffer, or all files in the directory if none are marked. Marked +subdirectories are handled recursively in the same way. + +This means copy the marked files of marked subdirs and their marked +subdirs, etc. It does not mean copy the subdirs themselves +recursively. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +Preserves the last-modified date when copying, unless +`dired-copy-preserve-time' is nil. + +The default suggested for the target directory depends on the value of +`dired-dwim-target', which see. + +This command copies symbolic links by creating new ones, like UNIX +command `cp -d'. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-create-files-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (let ((dired-recursive-copies nil)) ; Doesn't have to be nil, but let's not go overboard now. + (diredp-do-create-files-recursive #'dired-copy-file "Copy" ignore-marks-p details))) + +(defun diredp-do-create-files-recursive (file-creator operation ignore-marks-p &optional details) + "Create a new file for each marked file, including those in marked subdirs. +Like `dired-do-create-files', but act recursively on subdirs, and +always keep markings. +Prompts for the target directory, in which to create the files. +FILE-CREATOR and OPERATION are as in `dired-create-files'. +Non-nil IGNORE-MARKS-P means ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (lexical-let* ((fn-list (diredp-get-files ignore-marks-p nil nil nil nil details)) + (target-dir (dired-dwim-target-directory)) + (defaults (and (fboundp 'dired-dwim-target-defaults) ; Emacs 23+ + (dired-dwim-target-defaults fn-list target-dir))) + (target (expand-file-name + (if (fboundp 'minibuffer-with-setup-hook) ; Emacs 22+ + (minibuffer-with-setup-hook + (lambda () + (set (make-local-variable 'minibuffer-default-add-function) + nil) + (setq minibuffer-default defaults)) + (funcall (if (fboundp 'read-directory-name) + #'read-directory-name + #'read-file-name) + (concat operation " files to: ") + default-directory default-directory)) + (funcall (if (fboundp 'read-directory-name) + #'read-directory-name + #'read-file-name) + (concat operation "files to: ") + default-directory default-directory))))) + (unless (file-directory-p target) (error "Target is not a directory: `%s'" target)) + (dired-create-files + file-creator operation fn-list + #'(lambda (from) (expand-file-name (file-name-nondirectory from) target)) + ;; Hard-code `*' marker, or else it will be removed in lower dirs because the code uses + ;; `dired-file-marker', which only works in the current Dired directory. + ?*))) + +(defun diredp-create-files-non-directory-recursive (file-creator basename-constructor operation + &optional ignore-marks-p details) + "Apply FILE-CREATOR + BASENAME-CONSTRUCTOR to non-dir part of marked names. +Like `dired-create-files-non-directory', but act recursively on subdirs. + +The files acted on are those marked in the current Dired buffer, or +all files in the directory if none are marked. Marked subdirectories +are handled recursively in the same way. + +With non-nil IGNORE-MARKS-P, ignore all marks - include all files in +this Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (let (rename-non-directory-query) + (dired-create-files + file-creator + operation + (diredp-get-files ignore-marks-p nil nil nil nil details) + #'(lambda (from) + (let ((to (concat (file-name-directory from) + (funcall basename-constructor (file-name-nondirectory from))))) + (and (let ((help-form (format "\ +Type SPC or `y' to %s one file, DEL or `n' to skip to next, +`!' to %s all remaining matches with no more questions." + (downcase operation) + (downcase operation)))) + (dired-query 'rename-non-directory-query (concat operation " `%s' to `%s'") + (dired-make-relative from) (dired-make-relative to))) + to))) + ;; Hard-code `*' marker, or else it will be removed in lower dirs because the code uses + ;; `dired-file-marker', which only works in the current Dired directory. + ?*))) + +(defun diredp-do-chxxx-recursive (attribute-name program op-symbol &optional ignore-marks-p default details) + "Change attributes of the marked files, including those in marked subdirs. +Refresh their file lines. + +Like `dired-do-chxxx', but act recursively on subdirs. The subdirs +acted on are those that are marked in the current Dired buffer, or all +subdirs in the directory if none are marked. Marked subdirectories +are handled recursively in the same way. + +ATTRIBUTE-NAME is a string describing the attribute to the user. +PROGRAM is the program used to change the attribute. +OP-SYMBOL is the type of operation (for use in `dired-mark-pop-up'). +Non-nil IGNORE-MARKS-P means ignore all marks - include all files in this + Dired buffer and all subdirs, recursively. +DEFAULT is the default value for reading the mark string. +DETAILS is passed to `diredp-get-files' and + `diredp-do-redisplay-recursive'." + (let* ((this-buff (current-buffer)) + (files (diredp-get-files ignore-marks-p nil nil nil nil details)) + (prompt (concat "Change " attribute-name " of %s to: ")) + (new-attribute (if (> emacs-major-version 22) + (dired-mark-read-string prompt nil op-symbol ignore-marks-p files default) + (dired-mark-read-string prompt nil op-symbol ignore-marks-p files))) + (operation (concat program " " new-attribute)) + failures) + (setq failures (dired-bunch-files 10000 (function dired-check-process) + (append (list operation program) + (unless (string-equal new-attribute "") + (if (equal attribute-name "Timestamp") + (list "-t" new-attribute) + (list new-attribute))) + (and (diredp-string-match-p "gnu" system-configuration) + '("--"))) ; -------------------------------- + files)) + (with-current-buffer this-buff (diredp-do-redisplay-recursive details 'MSGP)) + (when failures (dired-log-summary (format "%s: error" operation) nil)))) + +;;;###autoload +(defun diredp-do-chmod-recursive (&optional ignore-marks-p details) ; Bound to `M-+ M' + "Change the mode of the marked files, including those in marked subdirs. +Symbolic modes like `g+w' are allowed. + +Note that marked subdirs are not changed. Their markings are used only +to indicate that some of their files are to be changed. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files' and `diredp-do-redisplay-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (let* ((files (diredp-get-files ignore-marks-p nil nil nil nil details)) + (modestr (and (stringp (car files)) (nth 8 (file-attributes (car files))))) + (default (and (stringp modestr) + (string-match "^.\\(...\\)\\(...\\)\\(...\\)$" modestr) + (replace-regexp-in-string "-" "" (format "u=%s,g=%s,o=%s" + (match-string 1 modestr) + (match-string 2 modestr) + (match-string 3 modestr))))) + (modes (if (> emacs-major-version 22) + (dired-mark-read-string + "Change mode of marked files here and below to: " nil 'chmod + nil files default) + (dired-mark-read-string + "Change mode of marked files here and below to: " nil 'chmod + nil files)))) + (when (equal modes "") (error "No file mode specified")) + (dolist (file files) + (set-file-modes file (or (and (diredp-string-match-p "^[0-7]+" modes) (string-to-number modes 8)) + (file-modes-symbolic-to-number modes (file-modes file))))) + (diredp-do-redisplay-recursive details 'MSGP))) + +(unless (memq system-type '(windows-nt ms-dos)) + (defun diredp-do-chgrp-recursive (&optional ignore-marks-p details) + "Change the group of the marked (or next ARG) files. +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-chxxx-recursive'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (diredp-do-chxxx-recursive "Group" "chgrp" 'chgrp ignore-marks-p nil details))) + +(unless (memq system-type '(windows-nt ms-dos)) + (defun diredp-do-chown-recursive (&optional ignore-marks-p details) + "Change the owner of the marked (or next ARG) files. +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-chxxx-recursive'." + (interactive (list current-prefix-arg diredp-list-file-attributes)) + (diredp-do-chxxx-recursive "Owner" dired-chown-program 'chown ignore-marks-p nil details))) + +;;;###autoload +(defun diredp-do-touch-recursive (&optional ignore-marks-p details) + "Change the timestamp of marked files, including those in marked subdirs. +This calls `touch'. Like `dired-do-touch', but act recursively on +subdirs. The subdirs inserted are those that are marked in the +current Dired buffer, or all subdirs in the directory if none are +marked. Marked subdirectories are handled recursively in the same +way. + +With a prefix argument, ignore all marks - include all files in this +Dired buffer and all subdirs, recursively. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-do-chxxx-recursive'." + (interactive (progn (diredp-get-confirmation-recursive) (list current-prefix-arg diredp-list-file-attributes))) + (diredp-do-chxxx-recursive "Timestamp" (if (boundp 'dired-touch-program) + dired-touch-program ; Emacs 22+ + "touch") + 'touch + ignore-marks-p + (format-time-string "%Y%m%d%H%M.%S" (current-time)) + details)) + +;;;###autoload +(defun diredp-do-redisplay-recursive (&optional details msgp) + "Redisplay marked file lines, including those in marked subdirs. +Non-nil MSGP means show status messages. +Like `dired-do-redisplay' with no args, but act recursively on +subdirs. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (progn (diredp-ensure-mode) + (unless (y-or-n-p "Act on all marked file lines in and UNDER this dir? ") + (error "OK, canceled")) + (list diredp-list-file-attributes t))) + (when msgp (message "Redisplaying...")) + (dolist (dir (cons default-directory + (diredp-get-files nil #'file-directory-p 'INCLUDE-SUBDIRS 'DONT-ASK nil details))) + (with-current-buffer (dired-noselect dir) + ;; `message' is much faster than making `dired-map-over-marks' show progress + (dired-uncache (if (consp dired-directory) (car dired-directory) dired-directory)) + (dired-map-over-marks + (let ((fname (dired-get-filename)) + ;; Postpone readin hook till we map over all marked files (Bug#6810). + (dired-after-readin-hook nil)) + (message "Redisplaying... %s" fname) + (dired-update-file-line fname)) + nil) + (run-hooks 'dired-after-readin-hook) + (dired-move-to-filename))) + (when msgp (message "Redisplaying...done"))) + + +;;; `diredp-marked(-other-window)' tries to treat SWITCHES, but SWITCHES seems to be ignored +;;; by `dired' when the DIRNAME arg is a cons, at least on MS Windows. I filed Emacs bug #952 +;;; on 2008-09-10, but this doesn't work in Emacs 20, 21, 22, or 23, so I don't know if it will +;;; ever be fixed. If it is declared a non-bug and it doesn't work on any platforms, then I'll +;;; remove SWITCHES here, alas. + +;;;###autoload +(defun diredp-marked (dirname &optional n switches) ; Not bound + "Open Dired on only the marked files or the next N files. +With a non-zero numeric prefix arg N, use the next abs(N) files. +A plain (`C-u'), zero, or negative prefix arg prompts for listing +switches as in command `dired'. + +Note that the marked files can include files in inserted +subdirectories, so the Dired buffer that is opened can contain files +from multiple directories in the same tree." + (interactive (progn (diredp-ensure-mode) + (let ((num (and current-prefix-arg + (atom current-prefix-arg) + (not (zerop (prefix-numeric-value current-prefix-arg))) + (abs (prefix-numeric-value current-prefix-arg))))) + (list (cons (generate-new-buffer-name (buffer-name)) (dired-get-marked-files t num)) + num + (and current-prefix-arg ; Switches + (or (consp current-prefix-arg) + (< (prefix-numeric-value current-prefix-arg) 0)) + (read-string "Dired listing switches: " dired-listing-switches)))))) + (unless (or n (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t)))) + (error "No marked files")) + (dired dirname switches)) + +;;;###autoload +(defun diredp-marked-other-window (dirname &optional n switches) ; Bound to `C-M-*' + "Same as `diredp-marked', but uses a different window." + (interactive (progn (diredp-ensure-mode) + (let ((num (and current-prefix-arg + (atom current-prefix-arg) + (not (zerop (prefix-numeric-value current-prefix-arg))) + (abs (prefix-numeric-value current-prefix-arg))))) + (list (cons (generate-new-buffer-name (buffer-name)) (dired-get-marked-files t num)) + num + (and current-prefix-arg ; Switches + (or (consp current-prefix-arg) + (< (prefix-numeric-value current-prefix-arg) 0)) + (read-string "Dired listing switches: " dired-listing-switches)))))) + (unless (or n (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t)))) + (error "No marked files")) + (dired-other-window dirname switches)) + + +;; Similar to `dired-mark-extension' in `dired-x.el'. +;; The difference is that this uses prefix arg to unmark, not to determine the mark character. +;;;###autoload +(defun diredp-mark/unmark-extension (extension &optional unmark-p) ; Bound to `* .' + "Mark all files with a certain EXTENSION for use in later commands. +A `.' is not automatically prepended to the string entered. +Non-nil prefix argument UNMARK-P means unmark instead of mark. + +Non-interactively, EXTENSION is the extension (a string). It can also + be a list of extension strings. +Optional argument UNMARK-P is the prefix arg." + (interactive (list (diredp-read-regexp (concat (if current-prefix-arg "UNmark" "Mark") "ing extension: ")) + current-prefix-arg)) + (or (listp extension) (setq extension (list extension))) + (dired-mark-files-regexp (concat ".";; Do not match names with nothing but an extension + "\\(" + (mapconcat #'regexp-quote extension "\\|") + "\\)$") + (and current-prefix-arg ?\040))) + +(defun diredp-mark-files-tagged-all/none (tags &optional none-p unmarkp prefix) + "Mark or unmark files tagged with all or none of TAGS. +TAGS is a list of strings, the tag names. +NONEP non-nil means mark/unmark files that have none of the TAGS. +UNMARKP non-nil means unmark; nil means mark. +PREFIX non-nil is the prefix of the autofile bookmarks to check. + +As a special case, if TAGS is empty, then mark or unmark the files +that have any tags at all, or if NONEP is non-nil then mark or unmark +those that have no tags at all." + (let ((dired-marker-char (if unmarkp ?\040 dired-marker-char))) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) (not (eolp)) + (let* ((fname (dired-get-filename nil t)) + (bmk (and fname (bmkp-get-autofile-bookmark fname nil prefix))) + (btgs (and bmk (bmkp-get-tags bmk))) + (presentp nil) + (allp (and btgs (catch 'diredp-m-f-t-an + (dolist (tag tags) + (setq presentp (assoc-default tag btgs nil t)) + (unless (if none-p (not presentp) presentp) + (throw 'diredp-m-f-t-an nil))) + t)))) + (if (null tags) + (if none-p (not btgs) btgs) + allp))) + (if none-p "no-tags-matching file" "all-tags-matching file")))) + +(defun diredp-mark-files-tagged-some/not-all (tags &optional notallp unmarkp prefix) + "Mark or unmark files tagged with any or not all of TAGS. +TAGS is a list of strings, the tag names. +NOTALLP non-nil means mark/unmark files that do not have all TAGS. +UNMARKP non-nil means unmark; nil means mark. +PREFIX non-nil is the prefix of the autofile bookmarks to check. + +As a special case, if TAGS is empty, then mark or unmark the files +that have any tags at all, or if NOTALLP is non-nil then mark or +unmark those that have no tags at all." + (let ((dired-marker-char (if unmarkp ?\040 dired-marker-char))) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) (not (eolp)) + (let* ((fname (dired-get-filename nil t)) + (bmk (and fname + (bmkp-get-autofile-bookmark fname nil prefix))) + (btgs (and bmk (bmkp-get-tags bmk))) + (presentp nil) + (allp (and btgs (catch 'diredp-m-f-t-sna + (dolist (tag tags) + (setq presentp (assoc-default tag btgs nil t)) + (when (if notallp (not presentp) presentp) + (throw 'diredp-m-f-t-sna t))) + nil)))) + (if (null tags) (if notallp (not btgs) btgs) allp))) + (if notallp "some-tags-not-matching file" "some-tags-matching file")))) + +;;;###autoload +(defun diredp-mark-files-tagged-all (tags &optional none-p prefix) ; `T m *' + "Mark all files that are tagged with *each* tag in TAGS. +As a special case, if TAGS is empty, then mark the files that have + any tags at all (i.e., at least one tag). +With a prefix arg, mark all that are *not* tagged with *any* TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-all/none tags none-p nil prefix)) + +;;;###autoload +(defun diredp-mark-files-tagged-none (tags &optional allp prefix) ; `T m ~ +' + "Mark all files that are not tagged with *any* tag in TAGS. +As a special case, if TAGS is empty, then mark the files that have + no tags at all. +With a prefix arg, mark all that are tagged with *each* tag in TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-all/none tags (not allp) nil prefix)) + +;;;###autoload +(defun diredp-mark-files-tagged-some (tags &optional somenotp prefix) ; `T m +' + "Mark all files that are tagged with *some* tag in TAGS. +As a special case, if TAGS is empty, then mark the files that have + any tags at all (i.e., at least one tag). +With a prefix arg, mark all that are *not* tagged with *all* TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-some/not-all tags somenotp nil prefix)) + +;;;###autoload +(defun diredp-mark-files-tagged-not-all (tags &optional somep prefix) ; `T m ~ *' + "Mark all files that are not tagged with *all* TAGS. +As a special case, if TAGS is empty, then mark the files that have + no tags at all. +With a prefix arg, mark all that are tagged with *some* TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-some/not-all tags (not somep) nil prefix)) + +;;;###autoload +(defun diredp-mark-files-tagged-regexp (regexp &optional notp prefix) ; `T m %' + "Mark files that have at least one tag that matches REGEXP. +With a prefix arg, mark all that are tagged but have no matching tags. +You need library `bookmark+.el' to use this command." + (interactive (list (read-string "Regexp: ") + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) (not (eolp)) + (lexical-let* ((fname (dired-get-filename nil t)) + (bmk (and fname + (bmkp-get-autofile-bookmark fname nil prefix))) + (btgs (and bmk (bmkp-get-tags bmk))) + (anyp (and btgs (bmkp-some #'(lambda (tag) + (diredp-string-match-p + regexp + (bmkp-tag-name tag))) + btgs)))) + (and btgs (if notp (not anyp) anyp)))) + "some-tag-matching-regexp file")) + +;;;###autoload +(defun diredp-unmark-files-tagged-regexp (regexp &optional notp prefix) ; `T u %' + "Unmark files that have at least one tag that matches REGEXP. +With a prefix arg, unmark all that are tagged but have no matching tags. +You need library `bookmark+.el' to use this command." + (interactive (list (read-string "Regexp: ") + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (let ((dired-marker-char ?\040)) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) (not (eolp)) + (lexical-let* ((fname (dired-get-filename nil t)) + (bmk (and fname (bmkp-get-autofile-bookmark fname nil prefix))) + (btgs (and bmk (bmkp-get-tags bmk))) + (anyp (and btgs (bmkp-some #'(lambda (tag) + (diredp-string-match-p + regexp + (bmkp-tag-name tag))) + btgs)))) + (and btgs (if notp (not anyp) anyp)))) + "some-tag-matching-regexp file"))) + +;;;###autoload +(defun diredp-unmark-files-tagged-all (tags &optional none-p prefix) ; `T u *' + "Unmark all files that are tagged with *each* tag in TAGS. +As a special case, if TAGS is empty, then unmark the files that have + any tags at all (i.e., at least one tag). +With a prefix arg, unmark all that are *not* tagged with *any* TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-all/none tags none-p 'UNMARK prefix)) + +;;;###autoload +(defun diredp-unmark-files-tagged-none (tags &optional allp prefix) ; `T u ~ +' + "Unmark all files that are *not* tagged with *any* tag in TAGS. +As a special case, if TAGS is empty, then unmark the files that have + no tags at all. +With a prefix arg, unmark all that are tagged with *each* tag in TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-all/none tags (not allp) 'UNMARK prefix)) + +;;;###autoload +(defun diredp-unmark-files-tagged-some (tags &optional somenotp prefix) ; `T u +' + "Unmark all files that are tagged with *some* tag in TAGS. +As a special case, if TAGS is empty, then unmark the files that have + any tags at all. +With a prefix arg, unmark all that are *not* tagged with *all* TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-some/not-all tags somenotp 'UNMARK prefix)) + +;;;###autoload +(defun diredp-unmark-files-tagged-not-all (tags &optional somep prefix) ; `T u ~ *' + "Unmark all files that are *not* tagged with *all* TAGS. +As a special case, if TAGS is empty, then unmark the files that have + no tags at all. +With a prefix arg, unmark all that are tagged with *some* TAGS. +You need library `bookmark+.el' to use this command." + (interactive (list (and (fboundp 'bmkp-read-tags-completing) (bmkp-read-tags-completing)) + current-prefix-arg + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark names: ")))) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark-files-tagged-some/not-all tags (not somep) 'UNMARK prefix)) + +;;;###autoload +(defun diredp-do-tag (tags &optional prefix arg) ; `T > +' + "Tag the marked (or the next prefix argument) files. +You need library `bookmark+.el' to use this command. + +Hit `RET' to enter each tag, then hit `RET' again after the last tag. +You can use completion to enter each tag. Completion is lax: you are +not limited to existing tags. + +TAGS is a list of strings. PREFIX is as for `diredp-do-bookmark'. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (bmkp-read-tags-completing) + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark name: ")) + current-prefix-arg))) + (dired-map-over-marks-check (lexical-let ((pref prefix)) #'(lambda () (diredp-tag tags pref))) + arg 'tag (diredp-fewer-than-2-files-p arg))) + +(defun diredp-tag (tags &optional prefix) + "Add tags to the file or directory named on the current line. +You need library `bookmark+.el' to use this function. +The bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Return nil for success, file name otherwise." + (bookmark-maybe-load-default-file) + (let ((file (dired-get-file-for-visit)) + failure) + (condition-case err + (bmkp-autofile-add-tags file tags nil prefix) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (dired-log failure) + (dired-make-relative file)))) ; Return file name for failure. + +;;;###autoload +(defun diredp-mouse-do-tag (event) ; Not bound + "In Dired, add some tags to this file. +You need library `bookmark+.el' to use this command." + (interactive "e") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'(lambda () (diredp-tag (bmkp-read-tags-completing) prefix)) + 1 'tag t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-do-untag (tags &optional prefix arg) ; `T > -' + "Remove some tags from the marked (or the next prefix arg) files. +You need library `bookmark+.el' to use this command. + +Hit `RET' to enter each tag, then hit `RET' again after the last tag. +You can use completion to enter each tag. Completion is lax: you are +not limited to existing tags. + +TAGS is a list of strings. PREFIX is as for `diredp-do-bookmark'. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (bmkp-read-tags-completing) + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + current-prefix-arg))) + (dired-map-over-marks-check (lexical-let ((pref prefix)) + #'(lambda () (diredp-untag tags pref))) + arg 'untag (diredp-fewer-than-2-files-p arg))) + +(defun diredp-untag (tags &optional prefix) + "Remove some tags from the file or directory named on the current line. +You need library `bookmark+.el' to use this function. +The bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Return nil for success, file name otherwise." + (bookmark-maybe-load-default-file) + (let ((file (dired-get-file-for-visit)) + failure) + (condition-case err + (bmkp-autofile-remove-tags file tags nil prefix) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (dired-log failure) + (dired-make-relative file)))) ; Return file name for failure. + +;;;###autoload +(defun diredp-mouse-do-untag (event) ; Not bound + "In Dired, remove some tags from this file. +You need library `bookmark+.el' to use this command." + (interactive "e") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (lexical-let* ((bmk (bmkp-get-autofile-bookmark (dired-get-filename) nil prefix)) + (btgs (and bmk (bmkp-get-tags bmk)))) + (unless btgs (error "File has no tags to remove")) + (dired-map-over-marks-check + #'(lambda () (diredp-untag (bmkp-read-tags-completing btgs) prefix)) 1 'untag t))) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-do-remove-all-tags (&optional prefix arg) ; `T > 0' + "Remove all tags from the marked (or the next prefix arg) files. +You need library `bookmark+.el' to use this command. + +PREFIX is as for `diredp-do-bookmark'. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + current-prefix-arg))) + (lexical-let ((pref prefix)) + (dired-map-over-marks-check #'(lambda () (diredp-remove-all-tags pref)) arg 'remove-all-tags + (diredp-fewer-than-2-files-p arg)))) + +(defun diredp-remove-all-tags (&optional prefix) + "Remove all tags from the file or directory named on the current line. +You need library `bookmark+.el' to use this function. +The bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Return nil for success, file name otherwise." + (bookmark-maybe-load-default-file) + (let ((file (dired-get-file-for-visit)) + failure) + (condition-case err + (bmkp-remove-all-tags (bmkp-autofile-set file nil prefix)) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (dired-log failure) + (dired-make-relative file)))) ; Return file name for failure. + +;;;###autoload +(defun diredp-mouse-do-remove-all-tags (event) ; Not bound + "In Dired, remove all tags from the marked (or next prefix arg) files. +You need library `bookmark+.el' to use this command." + (interactive "e") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'(lambda () (diredp-remove-all-tags prefix)) + 1 'remove-all-tags t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-do-paste-add-tags (&optional prefix arg) ; `T > p', `T > C-y' + "Add previously copied tags to the marked (or next prefix arg) files. +The tags were previously copied from a file to `bmkp-copied-tags'. +You need library `bookmark+.el' to use this command. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark name: ")) + current-prefix-arg))) + (dired-map-over-marks-check (lexical-let ((pref prefix)) + #'(lambda () (diredp-paste-add-tags pref))) + arg 'paste-add-tags + (diredp-fewer-than-2-files-p arg))) + +(defun diredp-paste-add-tags (&optional prefix) + "Add previously copied tags to the file or directory on the current line. +The tags were previously copied from a file to `bmkp-copied-tags'. +You need library `bookmark+.el' to use this function. +The bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Return nil for success, file name otherwise." + (bookmark-maybe-load-default-file) + (let ((file (dired-get-file-for-visit)) + failure) + (condition-case err + (bmkp-autofile-add-tags file bmkp-copied-tags nil prefix) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (dired-log failure) + (dired-make-relative file)))) ; Return file name for failure. + +;;;###autoload +(defun diredp-mouse-do-paste-add-tags (event) ; Not bound + "In Dired, add previously copied tags to this file. +The tags were previously copied from a file to `bmkp-copied-tags'. +You need library `bookmark+.el' to use this command." + (interactive "e") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'(lambda () (diredp-paste-add-tags prefix)) + 1 'paste-add-tags t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-do-paste-replace-tags (&optional prefix arg) ; `T > q' + "Replace tags for marked (or next prefix arg) files with copied tags. +The tags were previously copied from a file to `bmkp-copied-tags'. +You need library `bookmark+.el' to use this command. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for autofile bookmark name: ")) + current-prefix-arg))) + (dired-map-over-marks-check (lexical-let ((pref prefix)) + #'(lambda () (diredp-paste-replace-tags pref))) + arg 'paste-replace-tags (diredp-fewer-than-2-files-p arg))) + +(defun diredp-paste-replace-tags (&optional prefix) + "Replace tags for this file or dir with tags copied previously. +The tags were previously copied from a file to `bmkp-copied-tags'. +You need library `bookmark+.el' to use this function. +The bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Return nil for success, file name otherwise." + (bookmark-maybe-load-default-file) + (let ((file (dired-get-file-for-visit)) + failure) + (condition-case err + (progn (bmkp-remove-all-tags (bmkp-autofile-set file nil prefix)) + (bmkp-autofile-add-tags file bmkp-copied-tags nil prefix)) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (dired-log failure) + (dired-make-relative file)))) + +;;;###autoload +(defun diredp-mouse-do-paste-replace-tags (event) ; Not bound + "In Dired, replace tags for this file with tags copied previously. +The tags were previously copied from a file to `bmkp-copied-tags'. +You need library `bookmark+.el' to use this command." + (interactive "e") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'(lambda () (diredp-paste-replace-tags prefix)) + 1 'paste-replace-tags t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-do-set-tag-value (tag value &optional prefix arg) ; `T > v' + "Set TAG value to VALUE, for the marked (or next prefix arg) files. +This does not change the TAG name. +You need library `bookmark+.el' to use this command. + +PREFIX is as for `diredp-do-bookmark'. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (bmkp-read-tag-completing) + (read (read-string "Value: ")) + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + current-prefix-arg))) + (dired-map-over-marks-check (lexical-let ((tg tag) + (val value) + (pref prefix)) + #'(lambda () (diredp-set-tag-value tg val pref))) + arg 'set-tag-value (diredp-fewer-than-2-files-p arg))) + +(defun diredp-set-tag-value (tag value &optional prefix) + "Set TAG value to VALUE for this file or directory. +This does not change the TAG name. +You need library `bookmark+.el' to use this function. +The bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Return nil for success, file name otherwise." + (bookmark-maybe-load-default-file) + (let ((file (dired-get-file-for-visit)) + failure) + (condition-case err + (bmkp-set-tag-value (bmkp-autofile-set file nil prefix) tag value) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (dired-log failure) + (dired-make-relative file)))) ; Return file name for failure. + +;;;###autoload +(defun diredp-mouse-do-set-tag-value (event) ; Not bound + "In Dired, set the value of a tag for this file. +This does not change the tag name. +You need library `bookmark+.el' to use this command." + (interactive "e") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'(lambda () (diredp-set-tag-value (bmkp-read-tag-completing) + (read (read-string "Value: ")) + prefix)) + 1 'set-tag-value t)) + (diredp-previous-line 1)) + + +;; Define these even if `Bookmark+' is not loaded. +;;;###autoload +(defun diredp-mark-autofiles () ; Bound to `* B' + "Mark all autofiles, that is, files that have an autofile bookmark." + (interactive) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark/unmark-autofiles)) + +;;;###autoload +(defun diredp-unmark-autofiles () + "Unmark all autofiles, that is, files that have an autofile bookmark." + (interactive) + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (diredp-mark/unmark-autofiles t)) + +;;;###autoload +(defun diredp-mark/unmark-autofiles (&optional unmarkp) + "Mark all autofiles, or unmark if UNMARKP is non-nil." + (let ((dired-marker-char (if unmarkp ?\040 dired-marker-char))) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) (not (eolp)) + (let ((fname (dired-get-filename nil t))) + (and fname (bmkp-get-autofile-bookmark fname)))) + "autofile"))) + +(when (and (fboundp 'bmkp-get-autofile-bookmark) ; Defined in `bookmark+-1.el'. + (fboundp 'hlt-highlight-region)) ; Defined in `highlight.el'. + + (defun diredp-highlight-autofiles () + "Highlight files that are autofile bookmarks. +Highlighting uses face `diredp-autofile-name'." + (save-excursion + (goto-char (point-min)) + (while (re-search-forward dired-move-to-filename-regexp nil t) + ;; If Dired details are hidden the match data gets changed. + (let* ((bmk (save-match-data + (bmkp-get-autofile-bookmark (buffer-substring (match-end 0) (line-end-position))))) + (tags (and bmk (bmkp-get-tags bmk)))) + (when bmk + (hlt-highlight-region (match-end 0) (line-end-position) + (if tags + 'diredp-tagged-autofile-name + 'diredp-autofile-name))))))) + + (cond ((fboundp 'define-minor-mode) + ;; Emacs 21+. Use `eval' so that even if the library is byte-compiled with Emacs 20, + ;; loading it into Emacs 21+ will define variable `diredp-highlight-autofiles-mode'. + (eval '(define-minor-mode diredp-highlight-autofiles-mode + "Toggle automatic highlighting of autofile bookmarks. +When you turn this on, it ensures that your bookmark file is loaded. + +NOTE: This mode is ON BY DEFAULT. More precisely, when `dired+.el' is +loaded (for the first time per Emacs session), the mode is turned ON. +To prevent this and have the mode OFF by default, you must do one of +the following: + + * Put (diredp-highlight-autofiles-mode -1) in your init file, AFTER + it loads `dired+.el'. + + * Customize option `diredp-highlight-autofiles-mode' to `nil', AND + ensure that your `custom-file' (or the `custom-saved-variables' + part of your init file) is evaluated before `dired+.el' is loaded. + +You need libraries `Bookmark and `highlight.el' for this command." + :init-value t :global t :group 'Dired-Plus :require 'dired+ + (if (not diredp-highlight-autofiles-mode) + (remove-hook 'dired-after-readin-hook #'diredp-highlight-autofiles) + (add-hook 'dired-after-readin-hook #'diredp-highlight-autofiles) + (bookmark-maybe-load-default-file)) + (when (derived-mode-p 'dired-mode) (dired-revert nil nil)) + (when (interactive-p) + (message "Dired highlighting of autofile bookmarks is now %s" + (if diredp-highlight-autofiles-mode "ON" "OFF")))))) + (t;; Emacs 20. + (defun diredp-highlight-autofiles-mode (&optional arg) + "Toggle automatic highlighting of autofile bookmarks. +When you turn this on, it ensures that your bookmark file is loaded. + +NOTE: This mode is ON BY DEFAULT. More precisely, when `dired+.el' is +loaded (for the first time per Emacs session), the mode is turned ON. +To prevent this and have the mode OFF by default, you must do one of +the following: + + * Put (diredp-highlight-autofiles-mode -1) in your init file, AFTER + it loads `dired+.el'. + + * Customize option `diredp-highlight-autofiles-mode' to `nil', AND + ensure that your `custom-file' (or the `custom-saved-variables' + part of your init file) is evaluated before `dired+.el' is loaded. + +You need libraries `Bookmark and `highlight.el' for this command." + (interactive (list (or current-prefix-arg 'toggle))) + (setq diredp-highlight-autofiles-mode (if (eq arg 'toggle) + (not diredp-highlight-autofiles-mode) + (> (prefix-numeric-value arg) 0))) + (if (not diredp-highlight-autofiles-mode) + (remove-hook 'dired-after-readin-hook #'diredp-highlight-autofiles) + (add-hook 'dired-after-readin-hook #'diredp-highlight-autofiles) + (bookmark-maybe-load-default-file)) + (when (derived-mode-p 'dired-mode) (dired-revert nil nil)) + (when (interactive-p) (message "Dired highlighting of autofile bookmarks is now %s" + (if diredp-highlight-autofiles-mode "ON" "OFF")))))) + + ;; Turn it ON BY DEFAULT. + (unless (or (boundp 'diredp-loaded-p) (get 'diredp-highlight-autofiles-mode 'saved-value)) + (diredp-highlight-autofiles-mode 1)) + ) + +;;;###autoload +(defun diredp-do-bookmark (&optional prefix arg) ; Bound to `M-b' + "Bookmark the marked (or the next prefix argument) files. +Each bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Interactively, you are prompted for the PREFIX if + `diredp-prompt-for-bookmark-prefix-flag' is non-nil. +The bookmarked position is the beginning of the file. +If you use library `bookmark+.el' then the bookmark is an autofile. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive (progn (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + current-prefix-arg))) + (dired-map-over-marks-check (lexical-let ((pref prefix)) + #'(lambda () (diredp-bookmark pref nil 'NO-MSG-P))) + arg 'bookmark (diredp-fewer-than-2-files-p arg))) + +;;;###autoload +(defun diredp-mouse-do-bookmark (event) ; Not bound + "In Dired, bookmark this file. See `diredp-do-bookmark'." + (interactive "e") + (lexical-let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'(lambda () (diredp-bookmark prefix nil)) nil 'bookmark t)) + (diredp-previous-line 1)) + +(defun diredp-bookmark (&optional prefix file no-msg-p) + "Bookmark the file or directory FILE. +If you use library `bookmark+.el' then the bookmark is an autofile. +Return nil for success or the file name otherwise. + +The bookmark name is the (non-directory) file name, prefixed by + optional arg PREFIX (a string) if non-nil. + +FILE defaults to the file name on the current Dired line. + +Non-nil optional arg NO-MSG-P means do not show progress messages." + (bookmark-maybe-load-default-file) + (let ((fil (or file (dired-get-file-for-visit))) + (failure nil)) + (condition-case err + (if (fboundp 'bmkp-autofile-set) ; Bookmark+ - just set an autofile bookmark. + (bmkp-autofile-set fil nil prefix nil (not no-msg-p)) + ;; Vanilla `bookmark.el' (or very old Bookmark+ version). + (let ((bookmark-make-record-function + (cond ((and (require 'image nil t) (require 'image-mode nil t) + (condition-case nil (image-type fil) (error nil))) + ;; Last two lines of function are from `image-bookmark-make-record'. + ;; But don't use that directly, because it uses + ;; `bookmark-make-record-default', which gets nil for `filename'. + (lambda () + `((filename . ,fil) + (position . 0) + ;; NEED to keep this part of code sync'd with `bmkp-make-record-for-target-file'. + (image-type . ,(image-type fil)) + (handler . image-bookmark-jump)))) ; In `image-mode.el'. + (t + (lambda () + `((filename . ,fil) + (position . 0))))))) + (bookmark-store (concat prefix (file-name-nondirectory fil)) (cdr (bookmark-make-record)) nil))) + (error (setq failure (error-message-string err)))) + (if (not failure) + nil ; Return nil for success. + (if (fboundp 'bmkp-autofile-set) + (dired-log failure) + (dired-log "Failed to create bookmark for `%s':\n%s\n" fil failure)) + (dired-make-relative fil)))) ; Return file name for failure. + +;;;###autoload +(defun diredp-set-bookmark-file-bookmark-for-marked (bookmark-file ; Bound to `C-M-b' + &optional prefix arg) + "Bookmark the marked files and create a bookmark-file bookmark for them. +The bookmarked position is the beginning of the file. +Jumping to the bookmark-file bookmark loads the set of file bookmarks. +You need library `bookmark+.el' to use this command. + +Each bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Interactively, you are prompted for PREFIX if + `diredp-prompt-for-bookmark-prefix-flag' is non-nil. + +A prefix argument ARG specifies files to use instead of those marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories. + +You are also prompted for the bookmark file, BOOKMARK-FILE. The +default is `.emacs.bmk' in the current directory, but you can enter +any file name, anywhere. + +The marked-file bookmarks are added to file BOOKMARK-FILE, but this +command does not make BOOKMARK-FILE the current bookmark file. To +make it current, just jump to the bookmark-file bookmark created by +this command. That bookmark (which bookmarks BOOKMARK-FILE) is +defined in that current bookmark file. + +Example: + + Bookmark file `~/.emacs.bmk' is current before invoking this command. + The current (Dired) directory is `/foo/bar'. + The marked files are bookmarked in the (possibly new) bookmark file + `/foo/bar/.emacs.bmk'. + The bookmarks for the marked files have names prefixed by `FOOBAR '. + The name of the bookmark-file bookmark is `Foobar Files'. + Bookmark `Foobar Files' is itself in bookmark file `~/.emacs.bmk'. + Bookmark file `~/.emacs.bmk' is current after invoking this command. + +You are prompted for the name of the bookmark-file bookmark, the +BOOKMARK-FILE for the marked-file bookmarks, and a PREFIX string for +each of the marked-file bookmarks. + +See also command `diredp-do-bookmark-in-bookmark-file'." + (interactive (diredp-read-bookmark-file-args)) + (diredp-ensure-bookmark+) + (diredp-do-bookmark-in-bookmark-file bookmark-file prefix arg 'CREATE-BOOKMARK-FILE-BOOKMARK)) + +;;;###autoload +(defun diredp-do-bookmark-in-bookmark-file (bookmark-file ; Bound to `C-M-B' (aka `C-M-S-b') + &optional prefix arg bfile-bookmarkp files) + "Bookmark marked files in BOOKMARK-FILE and save BOOKMARK-FILE. +The files bookmarked are the marked files, by default. +The bookmarked position is the beginning of the file. +You are prompted for BOOKMARK-FILE. The default is `.emacs.bmk' in +the current directory, but you can enter any file name, anywhere. +You need library `bookmark+.el' to use this command. + +The marked files are bookmarked in file BOOKMARK-FILE, but this +command does not make BOOKMARK-FILE the current bookmark file. To +make it current, use `\\[bmkp-switch-bookmark-file]' (`bmkp-switch-bookmark-file'). + +Each bookmark name is the non-directory portion of the file name, + prefixed by PREFIX if it is non-nil. +Interactively, you are prompted for PREFIX if + `diredp-prompt-for-bookmark-prefix-flag' is non-nil. + +Interactively, a prefix argument ARG specifies the files to use +instead of those marked. + + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories. + +See also command `diredp-set-bookmark-file-bookmark-for-marked'. + +Non-interactively: + + * Non-nil BFILE-BOOKMARKP means create a bookmark-file bookmark for + BOOKMARK-FILE. + * Non-nil FILES is the list of files to bookmark." + (interactive (diredp-read-bookmark-file-args)) + (diredp-ensure-bookmark+) + (let ((bfile-exists-p (file-readable-p bookmark-file))) + (unless bfile-exists-p (bmkp-empty-file bookmark-file)) + (unless bmkp-current-bookmark-file (setq bmkp-current-bookmark-file bookmark-default-file)) + (let ((old-bmkp-current-bookmark-file bmkp-current-bookmark-file)) + (unwind-protect + (progn (bmkp-switch-bookmark-file bookmark-file) ; Changes `*-current-bookmark-file'. + (if files + (dolist (file files) (diredp-bookmark prefix file 'NO-MSG-P)) + (dired-map-over-marks-check + (lexical-let ((pref prefix)) #'(lambda () (diredp-bookmark pref nil 'NO-MSG-P))) + arg 'bookmark (diredp-fewer-than-2-files-p arg))) + (bookmark-save) + (unless bfile-exists-p (revert-buffer))) + (unless (bmkp-same-file-p old-bmkp-current-bookmark-file bmkp-current-bookmark-file) + (bmkp-switch-bookmark-file old-bmkp-current-bookmark-file 'NO-MSG)))) + (when bfile-bookmarkp (bmkp-set-bookmark-file-bookmark bookmark-file)))) + +(defun diredp-read-bookmark-file-args () + "Read args for `diredp-do-bookmark-in-bookmark-file' and similar." + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (let* ((insert-default-directory t) + (bmk-file (expand-file-name + (read-file-name + "Use bookmark file (default is in CURRENT dir): " nil + (if (or (> emacs-major-version 23) + (and (= emacs-major-version 23) (> emacs-minor-version 1))) + (list ".emacs.bmk" bookmark-default-file) + ".emacs.bmk"))))) + bmk-file) + (and diredp-prompt-for-bookmark-prefix-flag (read-string "Prefix for autofile bookmark names: ")) + current-prefix-arg)) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; Allows for consp `dired-directory' too. +;; +(defun dired-buffers-for-dir (dir &optional file) + "Return a list of buffers that Dired DIR (top level or in-situ subdir). +If FILE is non-nil, include only those whose wildcard pattern (if any) +matches FILE. +The list is in reverse order of buffer creation, most recent last. +As a side effect, killed Dired buffers for DIR are removed from +`dired-buffers'." + (setq dir (file-name-as-directory dir)) + (let (result buf) + (dolist (elt dired-buffers) + (setq buf (cdr elt)) + (cond ((null (buffer-name buf)) ; Buffer is killed - clean up. + (setq dired-buffers (delq elt dired-buffers))) + ((dired-in-this-tree dir (car elt)) + (with-current-buffer buf + (and (assoc dir dired-subdir-alist) + (or (null file) + (if (stringp dired-directory) + ;; Allow for consp `dired-directory' too. + (let ((wildcards (file-name-nondirectory (if (consp dired-directory) + (car dired-directory) + dired-directory)))) + (or (zerop (length wildcards)) + (diredp-string-match-p (dired-glob-regexp wildcards) file))) + (member (expand-file-name file dir) (cdr dired-directory)))) + (setq result (cons buf result))))))) + result)) + + +;; If you use library `files+.el', you need not use these commands +;; explicitly, because that library redefines `find-file-read-args' to +;; do the same thing, in Dired mode. These are provided here in case +;; you want to bind them directly - for example, in case your code +;; does not use `find-file-read-args'. +;; +;;;###autoload +(defun diredp-find-a-file (filename &optional wildcards) ; Not bound + "`find-file', but use file on current line as default (`M-n')." + (interactive (diredp-find-a-file-read-args "Find file: " nil)) + (find-file filename wildcards)) + +;;;###autoload +(defun diredp-find-a-file-other-frame (filename &optional wildcards) ; Not bound + "`find-file-other-frame', but use file under cursor as default (`M-n')." + (interactive (diredp-find-a-file-read-args "Find file: " nil)) + (find-file-other-frame filename wildcards)) + +;;;###autoload +(defun diredp-find-a-file-other-window (filename &optional wildcards) ; Not bound + "`find-file-other-window', but use file under cursor as default (`M-n')." + (interactive (diredp-find-a-file-read-args "Find file: " nil)) + (find-file-other-window filename wildcards)) + +;;;###autoload +(defun diredp-find-a-file-read-args (prompt mustmatch) ; Not bound + (list (lexical-let ((find-file-default (abbreviate-file-name (dired-get-file-for-visit)))) + (minibuffer-with-setup-hook (lambda () + (setq minibuffer-default find-file-default)) + (read-file-name prompt nil default-directory mustmatch))) + t)) + +;;;###autoload +(defun diredp-find-file-reuse-dir-buffer () ; Not bound + "Like `dired-find-file', but reuse Dired buffers. +Unlike `dired-find-alternate-file' this does not use +`find-alternate-file' unless (1) the target is a directory that is not +yet visited as a Dired buffer, and (2) the current (Dired) buffer is +not visited also in some other window (possibly in an iconified +frame)." + (interactive) + (set-buffer-modified-p nil) + (let ((file (dired-get-file-for-visit))) + (diredp--reuse-dir-buffer-helper file))) + +;;;###autoload +(defun diredp-mouse-find-file-reuse-dir-buffer (event &optional find-file-func find-dir-func) ; Not bound + "Like `dired-mouse-find-file', but reuse Dired buffers. +Unlike `dired-find-alternate-file' this does not use +`find-alternate-file' unless (1) the target is a directory that is not +yet visited as a Dired buffer, and (2) the current (Dired) buffer is +not visited also in some other window (possibly in an iconified +frame). + +Non-nil optional args FIND-FILE-FUNC and FIND-DIR-FUNC specify +functions to visit the file and directory, respectively. +Defaults: `find-file' and `dired', respectively." + (interactive "e") + (let (window pos file) + (save-excursion + (setq window (posn-window (event-end event)) + pos (posn-point (event-end event))) + (unless (windowp window) (error "No file chosen")) + (set-buffer (window-buffer window)) + (goto-char pos) + (setq file (dired-get-file-for-visit))) + (select-window window) + (diredp--reuse-dir-buffer-helper file find-file-func find-dir-func))) + +(defun diredp--reuse-dir-buffer-helper (file &optional find-file-func find-dir-func other-window) + "Helper for commands `diredp-*-reuse-dir-buffer' commands. +Non-nil optional args FIND-FILE-FUNC and FIND-DIR-FUNC specify +functions to visit the file and directory, respectively. +Defaults: `find-file' and `dired', respectively. + +Unlike `dired-find-alternate-file' this does not use +`find-alternate-file' unless (1) the target is a directory that is not +yet visited as a Dired buffer, and (2) the current (Dired) buffer is +not visited also in some other window (possibly in an iconified +frame)." + (setq find-file-func (or find-file-func (if other-window #'find-file-other-window #'find-file)) + find-dir-func (or find-dir-func (if other-window #'dired-other-window #'dired))) + (let (;; This binding prevents problems with preserving point in windows displaying Dired buffers, because + ;; reverting a Dired buffer empties it, which changes the places where the markers used by + ;; `switch-to-buffer-preserve-window-point' point. + (switch-to-buffer-preserve-window-point (and (boundp 'switch-to-buffer-preserve-window-point) ; Emacs 24+ + (or (not (boundp 'dired-auto-revert-buffer)) + (not dired-auto-revert-buffer)) + switch-to-buffer-preserve-window-point)) + (find-file-run-dired t) + (wins ()) + (alt-find-file-func (if other-window + #'find-alternate-file-other-window + #'find-alternate-file)) + dir-bufs) + (if (or (not (file-directory-p file)) ; New is a not a directory + (dired-buffers-for-dir file) ; or there is a Dired buffer for it, even as a subdir. + (and (setq dir-bufs (dired-buffers-for-dir default-directory)) ; Dired bufs for current (old). + (progn + (dolist (buf dir-bufs) + (setq wins (append wins (get-buffer-window-list buf 'NOMINI 0)))) + (setq wins (delq nil wins)) + (cdr wins)))) ; More than one window showing current Dired buffer. + (if (file-directory-p file) + (or (and (cdr dired-subdir-alist) (dired-goto-subdir file)) ; New is a subdir inserted in current + (funcall find-dir-func file)) + (funcall find-file-func (file-name-sans-versions file t))) + (funcall alt-find-file-func (file-name-sans-versions file t))))) + +;;;###autoload +(defalias 'toggle-diredp-find-file-reuse-dir 'diredp-toggle-find-file-reuse-dir) +;;;###autoload +(defun diredp-toggle-find-file-reuse-dir (force-p) ; Bound to `C-M-R' (aka `C-M-S-r') + "Toggle whether Dired `find-file' commands reuse directories. +This applies also to `dired-w32-browser' commands and +`diredp-up-directory'. + +A prefix arg specifies directly whether or not to reuse. + If its numeric value is non-negative then reuse; else do not reuse. + +To set the behavior as a preference (default behavior), put this in +your ~/.emacs, where VALUE is 1 to reuse or -1 to not reuse: + + (diredp-toggle-find-file-reuse-dir VALUE) + +Note: This affects only these commands: + + `dired-find-file' + `dired-mouse-find-file' + +It does not affect the corresponding `-other-window' commands. Note +too that, by default, mouse clicks to open files or directories open +in another window: command `diredp-mouse-find-file-other-window', not +`dired-mouse-find-file'. If you want a mouse click to reuse a +directory then bind `mouse-2' to `dired-mouse-find-file' instead." + (interactive "P") + (if force-p ; Force. + (if (natnump (prefix-numeric-value force-p)) + (diredp-make-find-file-keys-reuse-dirs) + (diredp-make-find-file-keys-not-reuse-dirs)) + (if (where-is-internal 'dired-find-file dired-mode-map 'ascii) + (diredp-make-find-file-keys-reuse-dirs) + (diredp-make-find-file-keys-not-reuse-dirs)))) + +(defun diredp-make-find-file-keys-reuse-dirs () + "Make find-file keys reuse Dired buffers." + (substitute-key-definition 'diredp-up-directory 'diredp-up-directory-reuse-dir-buffer dired-mode-map) + (substitute-key-definition 'dired-find-file 'diredp-find-file-reuse-dir-buffer dired-mode-map) + (substitute-key-definition 'dired-mouse-find-file 'diredp-mouse-find-file-reuse-dir-buffer dired-mode-map) + ;; These commands are defined in `w32-browser.el' (for use with MS Windows). + (substitute-key-definition 'dired-w32-browser 'dired-w32-browser-reuse-dir-buffer dired-mode-map) + (substitute-key-definition 'dired-mouse-w32-browser 'dired-mouse-w32-browser-reuse-dir-buffer dired-mode-map) + (message "Reusing Dired buffers is now ON")) + +(defun diredp-make-find-file-keys-not-reuse-dirs () + "Make find-file keys not reuse Dired buffers (i.e. act normally)." + (substitute-key-definition 'diredp-up-directory-reuse-dir-buffer 'diredp-up-directory dired-mode-map) + (substitute-key-definition 'diredp-find-file-reuse-dir-buffer 'dired-find-file dired-mode-map) + (substitute-key-definition 'diredp-mouse-find-file-reuse-dir-buffer 'dired-mouse-find-file dired-mode-map) + ;; These commands are defined in `w32-browser.el' (for use with MS Windows). + (substitute-key-definition 'dired-w32-browser-reuse-dir-buffer 'dired-w32-browser dired-mode-map) + (substitute-key-definition 'dired-mouse-w32-browser-reuse-dir-buffer 'dired-mouse-w32-browser dired-mode-map) + (message "Reusing Dired buffers is now OFF")) + +;;;###autoload +(defun diredp-omit-marked () ; Not bound + "Omit lines of marked files. Return the number of lines omitted." + (interactive) + (let ((old-modified-p (buffer-modified-p)) + count) + (when (interactive-p) (message "Omitting marked lines...")) + (setq count (dired-do-kill-lines nil "Omitted %d line%s.")) + (set-buffer-modified-p old-modified-p) ; So no `%*' appear in mode-line. + count)) + +;;;###autoload +(defun diredp-omit-unmarked () ; Not bound + "Omit lines of unmarked files. Return the number of lines omitted." + (interactive) + (let ((old-modified-p (buffer-modified-p)) + count) + (dired-toggle-marks) + (message "Omitting unmarked lines...") + (setq count (diredp-omit-marked)) + (dired-toggle-marks) ; Marks all except `.', `..' + (set-buffer-modified-p old-modified-p) ; So no `%*' appear in mode-line. + count)) + +;;;###autoload +(defun diredp-ediff (file2) ; Bound to `=' + "Compare file at cursor with file FILE2 using `ediff'. +FILE2 defaults to the file at the cursor as well. If you enter just a +directory name for FILE2, then the file at the cursor is compared with +a file of the same name in that directory. FILE2 is the second file +given to `ediff'; the file at the cursor is the first. + +Try to guess a useful default value for FILE2, as follows: + +* If the mark is active, use the file at mark. +* Else if the file at cursor is a autosave file or a backup file, use + the corresponding base file. +* Else if there is any backup file for the file at point, use the + newest backup file for it. +* Else use the file at point." + (interactive (progn (require 'ediff) + (list (ediff-read-file-name ; In `ediff-util.el'. + (format "Compare %s with" (dired-get-filename t)) + (dired-current-directory) + (let* ((file (dired-get-filename)) + (file-sans-dir (file-name-nondirectory file)) + (file-dir (file-name-directory file)) + (file-at-mark (and transient-mark-mode + mark-active + (save-excursion (goto-char (mark t)) + (dired-get-filename t t)))) + (last-backup (file-newest-backup file))) + (cond + (file-at-mark) + ((auto-save-file-name-p file-sans-dir) + (expand-file-name (substring file-sans-dir 1 -1) file-dir)) + ((backup-file-name-p file-sans-dir) + (expand-file-name (file-name-sans-versions file-sans-dir) file-dir)) + (last-backup) + (t file))))))) + (ediff-files (dired-get-filename) file2)) ; In `ediff.el'. + +(defun diredp-fewer-than-N-files-p (arg n) + "Return non-nil iff fewer than N files are to be treated by dired. +More precisely, return non-nil iff ARG is nil and fewer than N +files are marked, or the absolute value of ARG is less than N." + (if arg + (and (integerp arg) (< (abs arg) n)) ; Next or previous file (or none). + (not (save-excursion ; Fewer than two marked files. + (goto-char (point-min)) + (re-search-forward (dired-marker-regexp) nil t n))))) + +(defun diredp-fewer-than-2-files-p (arg) + "Return non-nil iff fewer than two files are to be treated by dired. +More precisely, return non-nil iff ARG is nil and fewer than two +files are marked, or ARG is -1, 0 or 1." + (diredp-fewer-than-N-files-p arg 2)) + +(defun diredp-fewer-than-echo-limit-files-p (arg) + "Return non-nil iff < `diredp-do-report-echo-limit' files marked. +More precisely, return non-nil iff ARG is nil and fewer than two +files are marked, or ARG is -1, 0 or 1." + (diredp-fewer-than-N-files-p arg diredp-do-report-echo-limit)) + +;;;###autoload +(defun diredp-do-apply-function (function &optional arg) ; Bound to `@' + "Apply FUNCTION to the marked files. +You are prompted for the FUNCTION. + +With a plain prefix ARG (`C-u'), visit each file and invoke FUNCTION + with no arguments. +Otherwise, apply FUNCTION to each file name. + +Any prefix arg other than single `C-u' behaves according to the ARG +argument of `dired-get-marked-files'. In particular, `C-u C-u' +operates on all files in the Dired buffer. + +The result returned for each file is logged by `dired-log'. Use `?' +to see all such results and any error messages. If there are fewer +marked files than `diredp-do-report-echo-limit' then each result is +also echoed momentarily." + (interactive (progn (diredp-ensure-mode) + (list (read (completing-read "Function: " obarray 'functionp nil nil + (and (boundp 'function-name-history) + 'function-name-history))) + current-prefix-arg))) + (let ((use-no-args-p (and (consp arg) (< (car arg) 16)))) + (when use-no-args-p (setq arg ())) + (save-selected-window + (diredp-map-over-marks-and-report + (if use-no-args-p #'diredp-invoke-function-no-args #'diredp-apply-function-to-file-name) + arg + 'apply\ function (diredp-fewer-than-2-files-p arg) + function + (diredp-fewer-than-echo-limit-files-p arg))))) + +(defun diredp-invoke-function-no-args (fun &optional echop) + "Visit file of this line at its beginning, then invoke function FUN. +No arguments are passed to FUN. +Log the result returned or any error. +Non-nil optional arg ECHOP means also echo the result." + (let* ((file (dired-get-filename)) + (failure (not (file-exists-p file))) + result) + (unless failure + (condition-case err + (with-current-buffer (find-file-noselect file) + (save-excursion + (goto-char (point-min)) + (setq result (funcall fun)))) + (error (setq failure err)))) + (diredp-report-file-result file result failure echop))) + +(defun diredp-apply-function-to-file-name (fun &optional echop) + "Apply function FUN to (absolute) file name on this line. +Log the result returned or any error. +Non-nil optional arg ECHOP means also echo the result." + (let ((file (dired-get-filename)) + (failure nil) + result) + (condition-case err + (setq result (funcall fun file)) + (error (setq failure err))) + (diredp-report-file-result file result failure echop))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +;; 1. Redisplay only if at most one file is being treated. +;; 2. Doc string reflects `Dired+'s version of `dired-map-over-marks-check'. +;; +;;;###autoload +(defun dired-do-compress (&optional arg) ; Bound to `Z' + "Compress or uncompress marked (or next prefix argument) files. +A prefix argument ARG specifies files to use instead of marked. + An integer means use the next ARG files (previous -ARG, if < 0). + `C-u': Use the current file (whether or not any are marked). + `C-u C-u': Use all files in Dired, except directories. + `C-u C-u C-u': Use all files and directories, except `.' and `..'. + `C-u C-u C-u C-u': Use all files and all directories." + (interactive "P") + (dired-map-over-marks-check #'dired-compress arg 'compress (diredp-fewer-than-2-files-p arg))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +;; 1. Redisplay only if at most one file is being treated. +;; 2. Doc string reflects `Dired+'s version of `dired-map-over-marks-check'. +;; +;;;###autoload +(defun dired-do-byte-compile (&optional arg) ; Bound to `B' + "Byte compile marked Emacs Lisp files. +A prefix argument ARG specifies files to use instead of those marked. + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file." + (interactive (let* ((arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list arg))) + (dired-map-over-marks-check #'dired-byte-compile arg 'byte-compile + (diredp-fewer-than-2-files-p arg))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +;; 1. Redisplay only if at most one file is being treated. +;; 2. Doc string reflects `Dired+' version of `dired-map-over-marks-check'. +;; +;;;###autoload +(defun dired-do-load (&optional arg) ; Bound to `L' + "Load the marked Emacs Lisp files. +A prefix argument ARG specifies files to use instead of those marked. + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file." + (interactive (let* ((arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list arg))) + (dired-map-over-marks-check #'dired-load arg 'load (diredp-fewer-than-2-files-p arg))) + + +(when (fboundp 'multi-isearch-files) + + ;; REPLACE ORIGINAL in `dired.el': + ;; + ;; 1. Added optional arg ARG, so you can act on next ARG files or on all files. + ;; 2. Added optional arg INTERACTIVEP. + ;; 3. Do not raise error if no files when not INTERACTIVEP. + ;; + (defun dired-do-isearch (&optional arg interactivep) + "Search for a string through all marked files using Isearch. +A prefix argument ARG specifies files to use instead of those marked. + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file. +When invoked interactively, raise an error if no files are marked." + (interactive (let* ((arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list arg t))) + (multi-isearch-files (dired-get-marked-files nil arg 'dired-nondirectory-p nil interactivep))) + + + ;; REPLACE ORIGINAL in `dired.el': + ;; + ;; 1. Added optional arg ARG, so you can act on next ARG files or on all files. + ;; 2. Added optional arg INTERACTIVEP. + ;; 3. Do not raise error if no files when not INTERACTIVEP. + ;; + (defun dired-do-isearch-regexp (&optional arg interactivep) + "Search for a regexp through all marked files using Isearch. +A prefix arg behaves as follows: + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file. +When invoked interactively, raise an error if no files are marked." + (interactive (let* ((arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list arg t))) + (multi-isearch-files-regexp (dired-get-marked-files nil arg 'dired-nondirectory-p nil interactivep))) + + ) + + +;; REPLACE ORIGINAL in `dired-aux.el': +;; +;; 1. Added optional arg ARG, so you can act on next ARG files or on all files. +;; 2. Added optional arg INTERACTIVEP. +;; 3. Do not raise error if no files when not INTERACTIVEP. +;; +;;;###autoload +(defun dired-do-search (regexp &optional arg interactivep) + "Search through all marked files for a match for REGEXP. +Stops when a match is found. +To continue searching for next match, use command \\[tags-loop-continue]. + +A prefix arg behaves as follows: + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file. + +When invoked interactively, raise an error if no files are marked." + (interactive (let* ((arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list (diredp-read-regexp "Search marked files (regexp): ") + arg + t))) + (tags-search regexp `(dired-get-marked-files nil ',arg 'dired-nondirectory-p nil ,interactivep))) + + +;; REPLACE ORIGINAL in `dired-aux.el': +;; +;; 1. Added optional arg ARG, so you can act on next ARG files or on all files. +;; 2. Added optional arg INTERACTIVEP. +;; 3. Do not raise error if no files when not INTERACTIVEP. +;; +;;;###autoload +(defun dired-do-query-replace-regexp (from to &optional arg interactivep) + "Do `query-replace-regexp' of FROM with TO, on all marked files. +NOTE: A prefix arg for this command acts differently than for other +commands, so that you can use it to request word-delimited matches. + +With a prefix argument: + * An odd number of plain `C-u': act on the marked files, but replace + only word-delimited matches. + * More than one plain `C-u': act on all files, ignoring whether any + are marked. + * Any other prefix arg: Act on the next numeric-prefix files. + +So for example: + * `C-u C-u C-u': act on all files, replacing word-delimited matches. + * `C-u 4': act on the next 4 files. `C-4' means the same thing. + * `C-u': act on the marked files, replacing word-delimited matches. + +When invoked interactively, raise an error if no files are marked. + +If you exit (\\[keyboard-quit], RET or q), you can resume the query replace +with the command \\[tags-loop-continue]." + (interactive (let ((common (query-replace-read-args "Query replace regexp in marked files" t t))) + (list (nth 0 common) + (nth 1 common) + current-prefix-arg + t))) + (let* ((argnum (and (consp arg) (prefix-numeric-value arg))) + (delimited (and argnum (eq (logand (truncate (log argnum 4)) 1) 1))) ; Odd number of plain `C-u'. + (all (and argnum (> argnum 4))) ; At least 3 plain `C-u'. + (dgmf-arg (dired-get-marked-files nil + (if (and arg (atom arg)) (abs arg) (and all '(16))) + 'dired-nondirectory-p + nil + interactivep))) + (dolist (file dgmf-arg) + (let ((buffer (get-file-buffer file))) + (when (and buffer (with-current-buffer buffer buffer-read-only)) + (error "File `%s' is visited read-only" file)))) + (tags-query-replace from to delimited `',dgmf-arg))) + + +(when (fboundp 'xref-collect-matches) ; Emacs 25+ + + + ;; REPLACE ORIGINAL in `dired-aux.el': + ;; + ;; 1. Added optional arg ARG, so you can act on next ARG files or on all files. + ;; 2. Added optional arg INTERACTIVEP. + ;; 3. Do not raise error if no files when not INTERACTIVEP. + ;; + (defun dired-do-find-regexp (regexp &optional arg interactivep) + "Find all matches for REGEXP in all marked files. +For any marked directory, all of its files are searched recursively. +However, files matching `grep-find-ignored-files' and subdirectories +matching `grep-find-ignored-directories' are skipped in the marked +directories. + +A prefix arg behaves as follows: + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file. + +When invoked interactively, raise an error if no files are marked. + +REGEXP should use constructs supported by your local `grep' command." + (interactive (let* ((arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list (diredp-read-regexp "Search marked files (regexp): ") + arg + t))) + (require 'grep) + (defvar grep-find-ignored-files) + (defvar grep-find-ignored-directories) + (let* ((files (dired-get-marked-files nil arg nil nil interactivep)) + (ignores (nconc (mapcar (lambda (s) (concat s "/")) grep-find-ignored-directories) + grep-find-ignored-files)) + (xrefs (mapcan (lambda (file) + (xref-collect-matches + regexp "*" file (and (file-directory-p file) ignores))) + files))) + (if xrefs + (xref--show-xrefs xrefs nil t) + (when interactivep (diredp-user-error "No matches for: %s" regexp))))) + + + ;; REPLACE ORIGINAL in `dired-aux.el': + ;; + ;; 1. Added optional arg ARG, so you can act on next ARG files or on all files. + ;; 2. Added optional arg INTERACTIVEP. + ;; 3. Do not raise error if no files when not INTERACTIVEP. + ;; +;;;###autoload + (defun dired-do-find-regexp-and-replace (from to &optional arg interactivep) + "Replace matches of FROM with TO, in all marked files. +For any marked directory, matches in all of its files are replaced, +recursively. However, files matching `grep-find-ignored-files' +and subdirectories matching `grep-find-ignored-directories' are skipped +in the marked directories. + +A prefix arg behaves as follows: + * An integer means use the next ARG files (previous -ARG, if < 0). + * Two or more `C-u' (e.g. `C-u C-u') means ignore any marks and use + all files in the Dired buffer. + * Any other prefix arg means use the current file. + +When invoked interactively, raise an error if no files are marked. + +REGEXP should use constructs supported by your local `grep' command." + (interactive (let ((common (query-replace-read-args "Query replace regexp in marked files" t t)) + (arg current-prefix-arg) + (C-u (and (consp arg) arg))) + (when (and C-u (> (prefix-numeric-value arg) 16)) (setq arg '(16))) + (list (nth 0 common) + (nth 1 common) + arg + t))) + (with-current-buffer (dired-do-find-regexp from arg interactivep) + (xref-query-replace-in-results from to))) + + ) + +;;;###autoload +(defun diredp-do-grep (command-args) ; Bound to `C-M-G' + "Run `grep' on marked (or next prefix arg) files. +A prefix argument behaves according to the ARG argument of +`dired-get-marked-files'. In particular, `C-u C-u' operates on all +files in the Dired buffer." + (interactive (progn (unless (if (< emacs-major-version 22) + grep-command + (and grep-command (or (not grep-use-null-device) (eq grep-use-null-device t)))) + (grep-compute-defaults)) + (list (diredp-do-grep-1)))) + (grep command-args)) + +;; Optional arg FILES is no longer used. It was used in `diredp-do-grep' before the +;; new `dired-get-marked-files'. +(defun diredp-do-grep-1 (&optional files) + "Helper function for `diredp-do-grep'. +Non-nil optional arg FILES are the files to grep, overriding the files +choice described for `diredp-do-grep'." + (let ((default (and (fboundp 'grep-default-command) + (if (fboundp 'grepp-default-regexp-fn) ; In `grep+.el'. + (grep-default-command (funcall (grepp-default-regexp-fn))) + (grep-default-command))))) + (read-from-minibuffer + "grep : " + (let ((up-to-files (concat grep-command " "))) + (cons (concat up-to-files + (mapconcat #'identity + (or files (mapcar 'shell-quote-argument + (dired-get-marked-files nil current-prefix-arg))) + " ")) + (- (length up-to-files) 2))) + nil nil 'grep-history default))) + +(when (memq system-type '(windows-nt ms-dos)) + (define-derived-mode diredp-w32-drives-mode fundamental-mode "Drives" + "Mode for Dired buffer listing MS Windows drives (local or remote)." + (setq buffer-read-only t))) + +;; The next two commands were originally taken from Emacs Wiki, page WThirtyTwoBrowseNetDrives: +;; https://www.emacswiki.org/emacs/WThirtyTwoBrowseNetDrives. They are referred to there as +;; commands `show-net-connections' and `netdir'. I am hoping that the contributor (anonymous) +;; does not mind my adapting them and including them in `Dired+'. + +(when (memq system-type '(windows-nt ms-dos)) + (defun diredp-w32-list-mapped-drives () ; Not bound + "List network connection information for shared MS Windows resources. +This just invokes the Windows `NET USE' command." + (interactive) + (shell-command "net use") + (display-buffer "*Shell Command Output*"))) + +(when (memq system-type '(windows-nt ms-dos)) + (defun diredp-w32-drives (&optional other-window-p) ; Bound to `:/' + "Visit a list of MS Windows drives for use by Dired. +With a prefix argument use another window for the list. +In the list, use `mouse-2' or `RET' to open Dired for a given drive. + +The drives listed are the remote drives currently available, as +determined by the Windows command `NET USE', plus the local drives +specified by option `diredp-w32-local-drives', which you can +customize. + +Note: When you are in Dired at the root of a drive (e.g. directory + `C:/'), command `diredp-up-directory' invokes this command. + So you can use `\\[diredp-up-directory]' to go up to the list of drives." + (interactive "P") + (require 'widget) + (let ((drive (copy-sequence diredp-w32-local-drives)) + (inhibit-read-only t)) + (with-temp-buffer + (insert (shell-command-to-string "net use")) + (goto-char (point-min)) + (while (re-search-forward "[A-Z]: +\\\\\\\\[^ ]+" nil t nil) + (setq drive (cons (split-string (match-string 0)) drive)))) + (if other-window-p + (pop-to-buffer "*Windows Drives*") + (if (fboundp 'pop-to-buffer-same-window) + (pop-to-buffer-same-window "*Windows Drives*") + (switch-to-buffer "*Windows Drives*"))) + (erase-buffer) + (widget-minor-mode 1) + (dolist (drv (sort drive (lambda (a b) (string-lessp (car a) (car b))))) + (lexical-let ((drv drv)) + (widget-create 'push-button + :notify (lambda (widget &rest ignore) (dired (car drv))) + (concat (car drv) " " (cadr drv)))) + (widget-insert "\n")) + (goto-char (point-min)) + (diredp-w32-drives-mode)))) + +;; $$$$$$ NO LONGER USED. Was used in `diredp-do-grep(-1)' before new `dired-get-marked-files'. +(defun diredp-all-files () + "List of all files shown in current Dired buffer. +Directories are not included." + (let ((pos (make-marker)) + (files ()) + file) + (save-excursion + (goto-char (point-min)) (beginning-of-line) + (while (not (eobp)) + (beginning-of-line) + (while (and (not (eobp)) (dired-between-files)) (forward-line 1)) + (save-excursion (forward-line 1) (move-marker pos (1+ (point)))) + (setq file (dired-get-filename nil t)) ; Non-nil second arg means "also . and ..". + (when file ; Remove directory portion if in same directory. + (setq file (dired-get-filename (dired-in-this-tree file default-directory) t))) + (unless (or (not file) (file-directory-p file)) (push file files)) + (goto-char pos)) + (move-marker pos nil)) + (setq files (sort files (if (and (featurep 'ls-lisp) + (not (symbol-value 'ls-lisp-use-insert-directory-program))) + 'ls-lisp-string-lessp + (if case-fold-search + (lambda (s1 s2) (string-lessp (upcase s1) (upcase s2))) + 'string-lessp)))))) + +(when (fboundp 'read-char-choice) ; Emacs 24+ + + + ;; REPLACE ORIGINAL in `dired-aux.el' + ;; + ;; `l' lists the files involved and prompts again. + ;; + (defun dired-query (sym prompt &rest args) + "Format PROMPT with ARGS, query user, and store the result in SYM. +The return value is either nil or t. + +The user can type: + `y' or `SPC' to accept once + `n' or `DEL' to skip once + `!' to accept this and subsequent queries + `l' list the files, showing details per `diredp-list-file-attributes' + `q' or `ESC' to decline this and subsequent queries + +If SYM is already bound to a non-nil value, this function may return +automatically without querying the user. If SYM is `!', return t; if +SYM is `q' or ESC, return nil." + (let* ((char (symbol-value sym)) + (char-choices '(?y ?\ ?n ?\177 ?! ?l ?q ?\e)) ; Use ?\ , not ?\s, for Emacs 20 byte-compiler. + (list-buf (generate-new-buffer-name "*Files*")) + (list-was-shown nil)) + (unwind-protect + (cond ((eq char ?!) t) ; Accept, and don't ask again. + ((memq char '(?q ?\e)) nil) ; Skip, and don't ask again. + (t ; No previous answer - ask now + (setq prompt (concat (apply (if (fboundp 'format-message) #'format-message #'format) + prompt + args) + (if help-form + (format " [Type ynlq! or %s] " (key-description (vector help-char))) + " [Type y, n, l, q or !] "))) + (set sym (setq char (read-char-choice prompt char-choices))) + (when (eq char ?l) ; List files and prompt again. + (diredp-list-files args nil nil nil diredp-list-file-attributes) + (set sym (setq char (read-char-choice prompt char-choices)))) + (and (memq char '(?y ?\ ?!)) t))) ; Use ?\ , not ?\s, for Emacs 20. + (when (get-buffer list-buf) + (save-window-excursion + (pop-to-buffer list-buf) + (condition-case nil ; Ignore error if user already deleted. + (if (one-window-p) (delete-frame) (delete-window)) + (error nil)) + (if list-was-shown (bury-buffer list-buf) (kill-buffer list-buf))))))) + + ) + +(unless (fboundp 'read-char-choice) ; Emacs 20-23 (modified the Emacs 23 version). Needs `dired-query-alist'. + + + ;; REPLACE ORIGINAL in `dired-aux.el' + ;; + ;; 1. `l' lists the files involved and prompts again. + ;; 2. Compatible with older Emacs versions (before Emacs 24): can use `dired-query-alist'. + ;; + (defun dired-query (qs-var qs-prompt &rest qs-args) + "Query user and return nil or t. +The user can type: + `y' or `SPC' to accept once + `n' or `DEL' to skip once + `!' to accept this and subsequent queries + `l' list the files, showing details per `diredp-list-file-attributes' + `q' or `ESC' to decline this and subsequent queries + +Store answer in symbol VAR (which must initially be bound to nil). +Format PROMPT with ARGS. +Binding variable `help-form' will help the user who types the help key." + (let* ((char (symbol-value qs-var)) + (dired-query-alist (cons '(?l . l) dired-query-alist)) + (action (cdr (assoc char dired-query-alist)))) + (cond ((eq 'yes action) t) ; Accept, and don't ask again. + ((eq 'no action) nil) ; Skip, and don't ask again. + (t ; No lasting effects from last time we asked - ask now. + (let ((cursor-in-echo-area t) + (executing-kbd-macro executing-kbd-macro) + (qprompt (concat qs-prompt + (if help-form + (format " [Type ynl!q or %s] " + (key-description (char-to-string help-char))) + " [Type y, n, l, q or !] "))) + done result elt) + (while (not done) + (apply #'message qprompt qs-args) + (setq char (set qs-var (read-event))) + (when (eq char ?l) ; List files and prompt again. + (diredp-list-files qs-args nil nil nil diredp-list-file-attributes) + (apply #'message qprompt qs-args) + (setq char (set qs-var (read-event)))) + (if (numberp char) + (cond ((and executing-kbd-macro (= char -1)) + ;; `read-event' returns -1 if we are in a keyboard macro and there are no more + ;; events in the macro. Try to get an event interactively. + (setq executing-kbd-macro nil)) + ((eq (key-binding (vector char)) 'keyboard-quit) (keyboard-quit)) + (t (setq done (setq elt (assoc char dired-query-alist))))))) + ;; Display the question with the answer. + (message "%s" (concat (apply #'format qprompt qs-args) (char-to-string char))) + (memq (cdr elt) '(t y yes))))))) + + ) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +;; 1. Use `diredp-this-subdir' instead of `dired-get-filename'. +;; 2. If on a subdir listing header line or a non-dir file in a subdir listing, go to +;; the line for the subdirectory in the parent directory listing. +;; 3. Fit one-window frame after inserting subdir. +;; +;;;###autoload +(defun dired-maybe-insert-subdir (dirname &optional switches no-error-if-not-dir-p) + ; Bound to `i' + "Move to Dired subdirectory line or subdirectory listing. +This bounces you back and forth between a subdirectory line and its +inserted listing header line. Using it on a non-directory line in a +subdirectory listing acts the same as using it on the subdirectory +header line. + +* If on a subdirectory line, then go to the subdirectory's listing, + creating it if not yet present. + +* If on a subdirectory listing header line or a non-directory file in + a subdirectory listing, then go to the line for the subdirectory in + the parent directory listing. + +* If on a non-directory file in the top Dired directory listing, do + nothing. + +Subdirectories are listed in the same position as for `ls -lR' output. + +With a prefix arg, you can edit the `ls' switches used for this +listing. Add `R' to the switches to expand the directory tree under a +subdirectory. + +Dired remembers the switches you specify with a prefix arg, so +reverting the buffer does not reset them. However, you might +sometimes need to reset some subdirectory switches after a +`dired-undo'. You can reset all subdirectory switches to the +default value using \\\\[dired-reset-subdir-switches]. See \ +Info node +`(emacs)Subdir switches' for more details." + (interactive (list (diredp-this-subdir) + (and current-prefix-arg + (read-string "Switches for listing: " + (or (and (boundp 'dired-subdir-switches) dired-subdir-switches) + dired-actual-switches))))) + (let ((opoint (point)) + (filename dirname)) + (cond ((consp filename) ; Subdir header line or non-directory file. + (setq filename (car filename)) + (if (assoc filename dired-subdir-alist) + (dired-goto-file filename) ; Subdir header line. + (dired-insert-subdir (substring (file-name-directory filename) 0 -1)))) + (t + ;; We don't need a marker for opoint as the subdir is always + ;; inserted *after* opoint. + (setq dirname (file-name-as-directory dirname)) + (or (and (not switches) (dired-goto-subdir dirname)) + (dired-insert-subdir dirname switches no-error-if-not-dir-p)) + ;; Push mark so that it's easy to go back. Do this after the + ;; insertion message so that the user sees the `Mark set' message. + (push-mark opoint) + (when (and (get-buffer-window (current-buffer)) ; Fit one-window frame. + (fboundp 'fit-frame-if-one-window)) ; In `autofit-frame.el'. + (fit-frame-if-one-window)))))) + +(defun diredp-this-subdir () + "This line's filename, if directory, or `dired-current-directory' list. +If on a directory line, then return the directory name. +Else return a singleton list of a directory name, which is as follows: + If on a subdirectory header line (either of the two lines), then use + that subdirectory name. Else use the parent directory name." + (or (let ((file (dired-get-filename nil t))) + (and file + (file-directory-p file) + (not (member (file-relative-name file (file-name-directory (directory-file-name file))) + '("." ".." "./" "../"))) + file)) + (list (dired-current-directory)))) + + +;; REPLACE ORIGINAL in `dired-aux.el' +;; +;; 1. Added optional arg FROM, which is also listed by `l' when prompted. +;; 2. Added missing doc string. +;; +(defun dired-handle-overwrite (to &optional from) + "Save old version of file TO that is to be overwritten. +`dired-overwrite-confirmed' and `overwrite-backup-query' are fluid vars +from `dired-create-files'. + +Optional arg FROM is a file being copied or renamed to TO. It is used +only when a user hits `l' to list files when asked whether to +overwrite." + (let (backup) + (when (and dired-backup-overwrite + dired-overwrite-confirmed + (setq backup (car (find-backup-file-name to))) + (or (eq 'always dired-backup-overwrite) + (dired-query 'overwrite-backup-query "Make backup for existing file `%s'? " to from))) + (rename-file to backup 0) ; Confirm overwrite of old backup. + (dired-relist-entry backup)))) + + +(when (fboundp 'dired-copy-file-recursive) ; Emacs 22+ + + + ;; REPLACE ORIGINAL in `dired-aux.el' + ;; + ;; 1. Pass also FROM to `dired-handle-overwrite', so `l' lists it too. + ;; 2. Added missing doc string. + ;; + (defun dired-copy-file (from to ok-if-already-exists) + "Copy file FROM to location TO. +Non-nil arg OK-IF-ALREADY-EXISTS is passed to `copy-file' or + `make-symbolic-link'. +Preserves the last-modified date when copying, unless +`dired-copy-preserve-time' is nil." + (dired-handle-overwrite to from) + (dired-copy-file-recursive from to ok-if-already-exists dired-copy-preserve-time t dired-recursive-copies)) + + + ;; REPLACE ORIGINAL in `dired-aux.el' + ;; + ;; 1. Pass also FROM to `dired-handle-overwrite', so `l' lists it too. + ;; 2. Added missing doc string. + ;; + (defun dired-copy-file-recursive (from to ok-if-already-exists &optional keep-time top recursive) + "Copy file FROM to location TO, handling directories in FROM recursively. +Non-nil arg OK-IF-ALREADY-EXISTS is passed to `copy-file' or + `make-symbolic-link'. +Non-nil optional arg KEEP-TIME is passed to `copy-file' or + `copy-directory'. +Non-nil optional arg TOP means do not bother with `dired-handle-overwrite'. +Non-nil optional arg RECURSIVE means recurse on any directories in + FROM, after confirmation if RECURSIVE is not `always'." + (when (and (eq t (car (file-attributes from))) (file-in-directory-p to from)) + (error "Cannot copy `%s' into its subdirectory `%s'" from to)) + (let ((attrs (file-attributes from))) + (if (and recursive + (eq t (car attrs)) + (or (eq recursive 'always) (yes-or-no-p (format "Recursive copies of %s? " from)))) + (copy-directory from to keep-time) + (or top (dired-handle-overwrite to from)) + (condition-case err + (if (stringp (car attrs)) ; It is a symlink + (make-symbolic-link (car attrs) to ok-if-already-exists) + (copy-file from to ok-if-already-exists keep-time)) + (file-date-error + (push (dired-make-relative from) dired-create-files-failures) + (dired-log "Can't set date on %s:\n%s\n" from err)))))) + + ) + + +;; REPLACE ORIGINAL in `dired-aux.el' +;; +;; 1. Pass also FILE to `dired-handle-overwrite', so `l' lists it too. +;; 2. Added missing doc string. +;; +(defun dired-rename-file (file newname ok-if-already-exists) + "Rename FILE to NEWNAME. +Non-nil arg OK-IF-ALREADY-EXISTS is passed to `rename-file'." + (dired-handle-overwrite newname file) + (rename-file file newname ok-if-already-exists) ; Error is caught in `-create-files'. + ;; Silently rename the visited file of any buffer visiting this file. + (and (get-file-buffer file) (with-current-buffer (get-file-buffer file) (set-visited-file-name newname nil t))) + (dired-remove-file file) + ;; See if it's an inserted subdir, and rename that, too. + (dired-rename-subdir file newname)) + + +;; REPLACE ORIGINAL in `dired-aux.el' +;; +;; Pass also FILE to `dired-handle-overwrite', so `l' lists it too. +;; +(defun dired-hardlink (file newname &optional ok-if-already-exists) + "Give FILE additional name NEWNAME. +Non-nil arg OK-IF-ALREADY-EXISTS is passed to `add-name-to-file'." + (dired-handle-overwrite newname file) + (add-name-to-file file newname ok-if-already-exists) ; Error is caught in -create-files'. + (dired-relist-file file)) ; Update the link count. + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; No-op: does nothing now. +;; +(defun dired-insert-subdir-validate (dirname &optional switches)) + + +;;; $$$$$$$$ +;;; ;; REPLACE ORIGINAL in `dired-aux.el'. +;;; ;; +;;; ;; 1. Do not require that DIRNAME be in the current directory tree (no error if not). +;;; ;; 2. Use `dolist' instead of `mapcar'. +;;; ;; +;;; (defun dired-insert-subdir-validate (dirname &optional switches) +;;; "Raise an error if it is invalid to insert DIRNAME with SWITCHES." +;;; ;;; (or (dired-in-this-tree dirname (expand-file-name default-directory)) ; REMOVED +;;; ;;; (error "%s: not in this directory tree" dirname)) +;;; (let ((real-switches (or switches (and (boundp 'dired-subdir-switches) ; Emacs 22+ +;;; dired-subdir-switches)))) +;;; (when real-switches +;;; (let (case-fold-search) +;;; (dolist (switchs '("F" "b")) ; Switches that matter for `dired-get-filename'. +;;; (unless (eq (null (diredp-string-match-p switchs real-switches)) +;;; (null (diredp-string-match-p switchs dired-actual-switches))) +;;; (error "Can't have dirs with and without `-%s' switches together" switchs))))))) + + +;; REPLACE ORIGINAL in `dired-aux.el'. +;; +;; If NEW-DIR is not a descendant of a directory in the buffer, put it at eob. +;; +(defun dired-insert-subdir-newpos (new-dir) + "Move to the proper position for inserting NEW-DIR, and return it. +Respect the order within each directory tree. But if NEW-DIR is not a +descendant of any directory in the buffer, then put it at the end." + (let ((alist dired-subdir-alist) + elt dir new-pos) + (while alist + (setq elt (car alist) + alist (cdr alist) + dir (car elt)) + (if (dired-tree-lessp dir new-dir) + (setq new-pos (dired-get-subdir-max elt) ; Position NEW-DIR after DIR. + alist ()) + (setq new-pos (point-max)))) + (goto-char new-pos)) + (unless (eobp) (forward-line -1)) + (insert "\n") + (point)) + + +;; This is like original `dired-hide-subdir' in `dired-aux.el', except: +;; +;; 1. Plain prefix arg means invoke `dired-hide-all'. Added optional arg NEXT. +;; 2. Do not move to the next subdir. +;; 3. Modified to work with also with older Emacs versions. +;; +(defun diredp-hide-subdir-nomove (arg &optional next) + "Hide or unhide the current directory. +Unlike `dired-hide-subdir', this does not advance the cursor to the +next directory header line. + +With a plain prefix arg (`C-u'), invoke `dired-hide-all' to hide or + show everything. +With a numeric prefix arg N, hide this subdirectory and the next N-1 + subdirectories." + (interactive "P") + (dired-hide-check) + (if (consp arg) + (dired-hide-all 'IGNORED) ; Arg needed for older Emacs versions. + (setq arg (prefix-numeric-value arg)) + (let ((modflag (buffer-modified-p))) + (while (>= (setq arg (1- arg)) 0) + (let* ((cur-dir (dired-current-directory)) + (hidden-p (dired-subdir-hidden-p cur-dir)) + (elt (assoc cur-dir dired-subdir-alist)) + (end-pos (1- (dired-get-subdir-max elt))) + buffer-read-only) + (goto-char (dired-get-subdir-min elt)) ; Keep header line visible, hide rest + (skip-chars-forward "^\n\r") + (if hidden-p + (subst-char-in-region (point) end-pos ?\r ?\n) + (subst-char-in-region (point) end-pos ?\n ?\r))) + (when next (dired-next-subdir 1 t))) + (if (fboundp 'restore-buffer-modified-p) + (restore-buffer-modified-p modflag) + (set-buffer-modified-p modflag))))) + +;;; ---------------------- +;;; If we instead renamed `diredp-hide-subdir-nomove' to `dired-hide-subdir' as a replacement, +;;; then we would define things this way: +;;; +;;; +;;; ;; REPLACE ORIGINAL in `dired-aux.el'. +;;; ;; +;;; ;; 1. Plain prefix arg means invoke `dired-hide-all'. Added optional arg NEXT. +;;; ;; +;;; ;; 2. Do not move to the next subdir. +;;; ;; +;;; ;; 3. Modified to work with also with older Emacs versions. +;;; ;; +;;; (defun dired-hide-subdir (arg &optional next) +;;; "Hide or unhide the current directory. +;;; Unlike `diredp-hide-subdir-goto-next', this does not advance the +;;; cursor to the next directory header line. +;;; +;;; With a plain prefix arg (`C-u'), invoke `dired-hide-all' to hide or +;;; show everything. +;;; With a numeric prefix arg N, hide this subdirectory and the next N-1 +;;; subdirectories." +;;; (interactive "P") +;;; (dired-hide-check) +;;; (if (consp arg) +;;; (dired-hide-all 'IGNORED) ; Arg needed for older Emacs versions. +;;; (setq arg (prefix-numeric-value arg)) +;;; (let ((modflag (buffer-modified-p))) +;;; (while (>= (setq arg (1- arg)) 0) +;;; (let* ((cur-dir (dired-current-directory)) +;;; (hidden-p (dired-subdir-hidden-p cur-dir)) +;;; (elt (assoc cur-dir dired-subdir-alist)) +;;; (end-pos (1- (dired-get-subdir-max elt))) +;;; buffer-read-only) +;;; (goto-char (dired-get-subdir-min elt)) ; Keep header line visible, hide rest +;;; (skip-chars-forward "^\n\r") +;;; (if hidden-p +;;; (subst-char-in-region (point) end-pos ?\r ?\n) +;;; (subst-char-in-region (point) end-pos ?\n ?\r))) +;;; (when next (dired-next-subdir 1 t))) +;;; (if (fboundp 'restore-buffer-modified-p) +;;; (restore-buffer-modified-p modflag) +;;; (set-buffer-modified-p modflag))))) +;;; +;;; (defun diredp-hide-subdir-goto-next (arg) +;;; "Hide or unhide current directory and move to next directory header line." +;;; (interactive "P") +;;; (dired-hide-subdir arg 'NEXT)) +;;; ---------------------- + + +;; REPLACE ORIGINAL in `dired-x.el'. +;; +;; Fix the `interactive' spec. This is the Emacs 24+ version, provided for earlier versions. +;; +(unless (> emacs-major-version 23) + (defun dired-mark-unmarked-files (regexp msg &optional unflag-p localp) + "Mark unmarked files matching REGEXP, displaying MSG. +REGEXP is matched against the entire file name. When called +interactively, prompt for REGEXP. +With prefix argument, unflag all those files. + +Non-interactively: + Returns t if any work was done, nil otherwise. + Optional fourth argument LOCALP is as in `dired-get-filename'." + (interactive (list (diredp-read-regexp "Mark unmarked files matching regexp (default all): ") + nil + current-prefix-arg + nil)) + (let ((dired-marker-char (if unflag-p ?\ dired-marker-char)) + (unmarkedp (eq (char-after) ?\ ))) + (diredp-mark-if (and (if unflag-p (not unmarkedp) unmarkedp) ; Fixes Emacs bug #27465. + (let ((fn (dired-get-filename localp 'NO-ERROR))) ; Uninteresting + (and fn (diredp-string-match-p regexp fn)))) + msg)))) + + +;; REPLACE ORIGINAL in `dired-x.el'. +;; +;; 1. Call `dired-get-marked-files' with original ARG, to get its multi-`C-u' behavior. +;; 2. Doc string updated to reflect change to `dired-simultaneous-find-file'. +;; 3. Added optional arg INTERACTIVEP. +;; 4. Do not raise error if no files when not INTERACTIVEP. +;; +;;;###autoload +(defun dired-do-find-marked-files (&optional arg interactivep) ; Bound to `F' + "Find marked files, displaying all of them simultaneously. +With no prefix argument: + +* If `pop-up-frames' is nil then split the current window across all + marked files, as evenly as possible. Remaining lines go to the + bottom-most window. The number of files that can be displayed this + way is restricted by the height of the current window and + `window-min-height'. + +* If `pop-up-frames' is non-nil then show each marked file in a + separate frame (not window). + +With a prefix argument: + +* One or more plain `C-u' behaves as for `dired-get-marked-files'. + In particular, `C-u C-u' means ignore any markings and operate on + ALL files and directories (except `.' and `..') in the Dired buffer. + +* A numeric prefix arg >= 0 means just find (visit) the marked files - + do not show them. + +* A numeric prefix arg < 0 means show each marked file in a separate + frame (not window). (This is the same behavior as no prefix arg + with non-nil `pop-up-frames'.) + +Note that a numeric prefix argument acts differently with this command +than it does with other `dired-do-*' commands: it does NOT act on the +next or previous (abs ARG) files, ignoring markings. + +To keep the Dired buffer displayed, split the window (e.g., `C-x 2') +first. To show only the marked files, type `\\[delete-other-windows]' first. + +When invoked interactively, raise an error if no files are marked." + (interactive "P\np") + (dired-simultaneous-find-file + (dired-get-marked-files nil (and (consp arg) arg) nil nil interactivep) + (and arg (prefix-numeric-value arg)))) + + +;; REPLACE ORIGINAL in `dired-x.el'. +;; +;; Use separate frames instead of windows if `pop-up-frames' is non-nil, +;; or if prefix arg is negative. +;; +(defun dired-simultaneous-find-file (file-list option) + "Visit all files in list FILE-LIST and display them simultaneously. +With non-nil OPTION >= 0, the files are found (visited) but not shown. + +If `pop-up-frames' is non-nil or if OPTION < 0, use a separate frame +for each file. (See also option `diredp-max-frames'.) + +Otherwise, the current window is split across all files in FILE-LIST, +as evenly as possible. Remaining lines go to the bottom-most window. +The number of files that can be displayed this way is restricted by +the height of the current window and the value of variable +`window-min-height'." + ;; This is not interactive because it is usually too clumsy to specify FILE-LIST interactively unless via dired. + (let (size) + (cond ((and option (natnump option)) + (while file-list (find-file-noselect (car file-list)) (pop file-list))) + ((or pop-up-frames option) + (let ((nb-files (length file-list))) + (when (and (> nb-files diredp-max-frames) + (not (y-or-n-p (format "Really show %d files in separate frames? " nb-files)))) + (error "OK, canceled")) + (while file-list (find-file-other-frame (car file-list)) (pop file-list)))) + (t + (setq size (/ (window-height) (length file-list))) + (when (> window-min-height size) (error "Too many files to show simultaneously")) + (find-file (car file-list)) + (pop file-list) + (while file-list + ;; Vertically split off a window of desired size. Upper window will have SIZE lines. + ;; Select lower (larger) window. We split it again. + (select-window (split-window nil size)) + (find-file (car file-list)) + (pop file-list)))))) + + +;;;;;; REPLACE ORIGINAL in both `dired.el' and `dired-x.el': +;;;;;; +;;;;;; 1. This incorporates the `dired-x.el' change to the `dired.el' +;;;;;; definition. This version works with or without using dired-x. +;;;;;; The `dired-x.el' version respects the var `dired-find-subdir'. +;;;;;; When `dired-find-subdir' is non-nil, this version is the same +;;;;;; as the `dired-x.el' version, except that a bug is corrected: +;;;;;; Whenever the argument to `dired-find-buffer-nocreate' is a cons, +;;;;;; the call to `dired-buffers-for-dir' gave a wrong type error. +;;;;;; This has been avoided by not respecting `dired-find-subdir' +;;;;;; whenever `dired-find-buffer-nocreate' is a cons. +;;;;;; For the case when `dired-find-subdir' is nil, see #2, below. +;;;;;; +;;;;;; 2. Unless `dired-find-subdir' is bound and non-nil: +;;;;;; If both DIRNAME and `dired-directory' are conses, then only +;;;;;; compare their cars (directories), not their explicit file lists +;;;;;; too. If equal, then update `dired-directory's file list to that +;;;;;; of DIRNAME. +;;;;;; +;;;;;; This prevents `dired-internal-noselect' (which is currently +;;;;;; `dired-find-buffer-nocreate's only caller) from creating a new +;;;;;; buffer in this case whenever a different set of files is present +;;;;;; in the cdr of DIRNAME and DIRNAME represents the same buffer as +;;;;;; `dired-directory'. +;;;;;; +;;;;;; If only one of DIRNAME and `dired-directory' is a cons, then +;;;;;; this returns nil. +;;;;;;;###autoload +;;;;(defun dired-find-buffer-nocreate (dirname &optional mode) +;;;; (let ((atomic-dirname-p (atom dirname))) +;;;; (if (and (boundp 'dired-find-subdir) dired-find-subdir atomic-dirname-p) +;;;; ;; This is the `dired-x.el' change: +;;;; (let* ((cur-buf (current-buffer)) +;;;; (buffers (nreverse (dired-buffers-for-dir dirname))) +;;;; (cur-buf-matches (and (memq cur-buf buffers) +;;;; ;; Files list (wildcards) must match, too: +;;;; (equal dired-directory dirname)))) +;;;; (setq buffers (delq cur-buf buffers)) ; Avoid using same buffer--- +;;;; (or (car (sort buffers (function dired-buffer-more-recently-used-p))) +;;;; (and cur-buf-matches cur-buf))) ; ---unless no other possibility. +;;;; ;; Comment from `dired.el': +;;;; ;; This differs from `dired-buffers-for-dir' in that it doesn't consider +;;;; ;; subdirs of `default-directory' and searches for the first match only. +;;;; (let ((blist dired-buffers) ; was (buffer-list) +;;;; found) +;;;; (or mode (setq mode 'dired-mode)) +;;;; (while blist +;;;; (if (null (buffer-name (cdr (car blist)))) +;;;; (setq blist (cdr blist)) +;;;; (save-excursion +;;;; (set-buffer (cdr (car blist))) +;;;; (if (not (and (eq major-mode mode) +;;;; ;; DIRNAME and `dired-directory' have the same dir, +;;;; ;; and if either of them has an explicit file list, +;;;; ;; then both of them do. In that case, update +;;;; ;; `dired-directory's file list from DIRNAME. +;;;; (if atomic-dirname-p +;;;; (and (atom dired-directory) ; Both are atoms. +;;;; (string= (file-truename dirname) +;;;; (file-truename dired-directory))) +;;;; (and (consp dired-directory) ; Both are conses. +;;;; (string= +;;;; (file-truename (car dirname)) +;;;; (file-truename (car dired-directory))) +;;;; ;; Update `dired-directory's file list. +;;;; (setq dired-directory dirname))))) +;;;; (setq blist (cdr blist)) +;;;; (setq found (cdr (car blist))) +;;;; (setq blist nil))))) +;;;; found)))) + + +;; REPLACE ORIGINAL in `dired-x.el'. +;; +;; Require confirmation. Fixes Emacs bug #13561. +;; +(defun dired-do-run-mail () + "If `dired-bind-vm' is non-nil, call `dired-vm', else call `dired-rmail'." + (interactive) + (unless (y-or-n-p "Read all marked mail folders? ") (error "OK, canceled")) + (if dired-bind-vm + ;; Read mail folder using vm. + (dired-vm) + ;; Read mail folder using rmail. + (dired-rmail))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; 1. Put `mouse-face' on whole line, not just file name. +;; 2. Add text property `dired-filename' to only the file name. +;; 3. Show image-file preview on mouseover, if `tooltip-mode' +;; and if `diredp-image-preview-in-tooltip'. +;; +(defun dired-insert-set-properties (beg end) + "Add various text properties to the lines in the region. +Highlight entire line upon mouseover. +Add text property `dired-filename' to the file name. +Handle `dired-hide-details-mode' invisibility spec (Emacs 24.4+)." + (let ((inhibit-field-text-motion t)) ; Just in case. + (save-excursion + (goto-char beg) + (while (< (point) end) + (condition-case nil + (cond ((dired-move-to-filename) + (add-text-properties (line-beginning-position) (line-end-position) + '(mouse-face highlight help-echo diredp-mouseover-help)) + (put-text-property + (point) (save-excursion (dired-move-to-end-of-filename) (point)) + 'dired-filename t) + (when (fboundp 'dired-hide-details-mode) ; Emacs 24.4+ + (put-text-property (+ (line-beginning-position) 1) (1- (point)) + 'invisible 'dired-hide-details-detail) + (dired-move-to-end-of-filename) + (when (< (+ (point) 4) (line-end-position)) + (put-text-property (+ (point) 4) (line-end-position) + 'invisible 'dired-hide-details-link)))) + ((fboundp 'dired-hide-details-mode) ; Emacs 24.4+ + (unless (or (diredp-looking-at-p "^$") (diredp-looking-at-p dired-subdir-regexp)) + (put-text-property (line-beginning-position) (1+ (line-end-position)) + 'invisible 'dired-hide-details-information)))) + (error nil)) + (forward-line 1))))) + +(defun diredp-mouseover-help (window buffer pos) + "Show `help-echo' help for a file name, in Dired. +If `tooltip-mode' is on and `diredp-image-preview-in-tooltip' says to +show an image preview, then do so. Otherwise, show text help." + (let ((image-dired-thumb-width (or (and (wholenump diredp-image-preview-in-tooltip) + diredp-image-preview-in-tooltip) + image-dired-thumb-width)) + (image-dired-thumb-height (or (and (wholenump diredp-image-preview-in-tooltip) + diredp-image-preview-in-tooltip) + image-dired-thumb-height)) + file) + (or (and (boundp 'tooltip-mode) tooltip-mode + (fboundp 'image-file-name-regexp) ; Emacs 22+, `image-file.el'. + diredp-image-preview-in-tooltip + (condition-case nil + (and (with-current-buffer buffer + (save-excursion (goto-char pos) + (diredp-string-match-p + (image-file-name-regexp) + (setq file (if (derived-mode-p 'dired-mode) + (dired-get-filename nil 'NO-ERROR) + ;; Make it work also for `diredp-list-files' listings. + (buffer-substring-no-properties (line-beginning-position) + (line-end-position))))))) + (or (not diredp-auto-focus-frame-for-thumbnail-tooltip-flag) + (progn (select-frame-set-input-focus (window-frame window)) t)) + (let ((img-file (if (eq 'full diredp-image-preview-in-tooltip) + file + (diredp-image-dired-create-thumb file)))) + (propertize " " 'display (create-image img-file)))) + (error nil))) + (if (fboundp 'describe-file) ; Library `help-fns+.el' + "mouse-2: visit in another window, C-h RET: describe" + "mouse-2: visit this file/dir in another window")))) + +;; `dired-hide-details-mode' enhancements. +(when (fboundp 'dired-hide-details-mode) ; Emacs 24.4+ + + (defun diredp-hide-details-if-dired () + "In Dired mode hide details. Outside Dired, do nothing." + (when (derived-mode-p 'dired-mode) (dired-hide-details-mode 1))) + + ;; Use `eval' of list so file byte-compiled in Emacs 20 will be OK in later versions. + (eval '(define-globalized-minor-mode global-dired-hide-details-mode + dired-hide-details-mode diredp-hide-details-if-dired)) + + (eval '(define-minor-mode dired-hide-details-mode + "Hide details in Dired mode." + (and diredp-hide-details-propagate-flag diredp-hide-details-last-state) + :group 'dired + (unless (derived-mode-p 'dired-mode) (error "Not a Dired buffer")) + (dired-hide-details-update-invisibility-spec) + (setq diredp-hide-details-toggled t) + (when diredp-hide-details-propagate-flag + (setq diredp-hide-details-last-state dired-hide-details-mode)) + (if dired-hide-details-mode + (add-hook 'wdired-mode-hook 'dired-hide-details-update-invisibility-spec nil t) + (remove-hook 'wdired-mode-hook 'dired-hide-details-update-invisibility-spec t)))) + + (defun diredp-hide/show-details () + "Hide/show details according to user options. +If `diredp-hide-details-propagate-flag' is non-nil and details have +never been hidden in the buffer, then hide/show according to your last +hide/show choice in any other Dired buffer or, if no last choice, +according to option `diredp-hide-details-initially-flag'." + (unless (or diredp-hide-details-toggled ; No op if hide/show already set. + (buffer-narrowed-p)) ; No-op when showing just newly copied file etc. + (cond (diredp-hide-details-propagate-flag + (dired-hide-details-mode (if diredp-hide-details-last-state 1 -1))) + (diredp-hide-details-initially-flag + (dired-hide-details-mode 1))))) + + (add-hook 'dired-after-readin-hook #'diredp-hide/show-details) + + (defun diredp-fit-frame-unless-buffer-narrowed () + "Fit frame unless Dired buffer is narrowed. +Requires library `autofit-frame.el'." + (when (and (get-buffer-window (current-buffer)) (not (buffer-narrowed-p))) + (fit-frame-if-one-window))) + + ;; Fit frame only if not narrowed. Put it on this hook because `dired-hide-details-mode' is + ;; invoked from `dired-after-readin-hook' via `diredp-hide/show-details', even for an update + ;; such as copying a file, where buffer is narrowed when invoked. + (when (fboundp 'fit-frame-if-one-window) ; In `autofit-frame.el'. + (add-hook 'dired-hide-details-mode-hook #'diredp-fit-frame-unless-buffer-narrowed))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; Reset `mode-line-process' to nil. +;; +(when (< emacs-major-version 21) + (or (fboundp 'old-dired-revert) (fset 'old-dired-revert (symbol-function 'dired-revert))) + (defun dired-revert (&optional arg noconfirm) + (setq mode-line-process nil) ; Set by, e.g., `find-dired'. + (old-dired-revert arg noconfirm))) + +;; Like `dired-up-directory', but go up to MS Windows drive if in top-level directory. +;; +;;;###autoload +(defun diredp-up-directory (&optional other-window) ; Bound to `^' + "Run Dired on parent directory of current directory. +Find the parent directory either in this buffer or another buffer. +Creates a buffer if necessary. + +With a prefix arg, Dired the parent directory in another window. + +On MS Windows, if you are already at the root directory, invoke +`diredp-w32-drives' to visit a navigable list of Windows drives." + (interactive "P") + (let* ((dir (dired-current-directory)) + (up (file-name-directory (directory-file-name dir)))) + (or (dired-goto-file (directory-file-name dir)) + ;; Only try `dired-goto-subdir' if buffer has more than one dir. + (and (cdr dired-subdir-alist) (dired-goto-subdir up)) + (progn (if other-window (dired-other-window up) (dired up)) + (dired-goto-file dir)) + (and (memq system-type '(windows-nt ms-dos)) (diredp-w32-drives other-window))))) + +;;;###autoload +(defun diredp-up-directory-reuse-dir-buffer (&optional other-window) ; Not bound + "Like `diredp-up-directory', but reuse Dired buffers. +With a prefix arg, Dired the parent directory in another window. + +On MS Windows, moving up from a root Dired buffer does not kill that +buffer (the Windows drives buffer is not really a Dired buffer)." + (interactive "P") + (let* ((dir (dired-current-directory)) + (dirfile (directory-file-name dir)) + (up (file-name-directory dirfile))) + (or (dired-goto-file dirfile) + ;; Only try `dired-goto-subdir' if buffer has more than one dir. + (and (cdr dired-subdir-alist) (dired-goto-subdir up)) ; It is a subdir inserted in current Dired. + (progn (diredp--reuse-dir-buffer-helper up nil nil other-window) + (dired-goto-file dir)) + (and (memq system-type '(windows-nt ms-dos)) (diredp-w32-drives other-window))))) + +;; Differs from `dired-next-line' in both wraparound and respect of `goal-column'. +;; +;;;###autoload +(defun diredp-next-line (arg) ; Bound to `SPC', `n', `C-n', `down' + "Move down lines then position cursor at filename. +If `goal-column' is non-nil then put the cursor at that column. +Optional prefix ARG says how many lines to move; default is one line. + +If `diredp-wrap-around-flag' is non-nil then wrap around if none is +found before the buffer end (buffer beginning, if ARG is negative). +Otherwise, just move to the buffer limit." + (interactive (let ((narg (prefix-numeric-value current-prefix-arg))) + (when (and (boundp 'shift-select-mode) shift-select-mode) (handle-shift-selection)) ; Emacs 23+ + (list narg))) ; Equivalent to "^p" + (let* ((line-move-visual nil) + ;; (goal-column nil) + + ;; Use `condition-case' and `(progn... t)' because Emacs < 22 `line-move' has no + ;; NO-ERROR arg and it always returns nil. + (no-more (or (not (condition-case nil (progn (line-move arg) t) (error nil))) + (if (< arg 0) (bobp) (eobp))))) + (when (and diredp-wrap-around-flag no-more) + (let ((diredp-wrap-around-flag nil)) + (goto-char (if (< arg 0) (point-max) (point-min))) + (diredp-next-line arg))) + ;; We never want to move point into an invisible line. + (while (and (fboundp 'invisible-p) ; Emacs 22+ + (invisible-p (point)) + (not (if (and arg (< arg 0)) (bobp) (eobp)))) + (forward-char (if (and arg (< arg 0)) -1 1))) + (unless goal-column (dired-move-to-filename)))) + +;; In Emacs < 22, `C-p' does not wrap around, because it never moves to the first header line. +;;;###autoload +(defun diredp-previous-line (arg) ; Bound to `p', `C-p', `up' + "Move up lines then position cursor at filename. +If `goal-column' is non-nil then put the cursor at that column. +Optional prefix ARG says how many lines to move; default is one line. + +If `diredp-wrap-around-flag' is non-nil then wrap around if none is +found before the buffer beginning (buffer end, if ARG is negative). +Otherwise, just move to the buffer limit." + (interactive (let ((narg (prefix-numeric-value current-prefix-arg))) + (when (and (boundp 'shift-select-mode) shift-select-mode) (handle-shift-selection)) ; Emacs 23+ + (list narg))) ; Equivalent to "^p" + (diredp-next-line (- (or arg 1)))) + +;;;###autoload +(defun diredp-next-dirline (arg &optional opoint) ; Bound to `>' + "Goto ARGth next directory file line. +If `diredp-wrap-around-flag' is non-nil then wrap around if none is +found before the buffer beginning (buffer end, if ARG is negative). +Otherwise, raise an error or, if NO-ERROR-IF-NOT-FOUND is nil, return +nil." + (interactive (let ((narg (prefix-numeric-value current-prefix-arg))) + (when (and (boundp 'shift-select-mode) shift-select-mode) (handle-shift-selection)) ; Emacs 23+ + (list narg))) ; Equivalent to "^p" + (or opoint (setq opoint (point))) + (if (if (> arg 0) + (re-search-forward dired-re-dir nil t arg) + (beginning-of-line) + (re-search-backward dired-re-dir nil t (- arg))) + (dired-move-to-filename) ; user may type `i' or `f' + (if diredp-wrap-around-flag + (let ((diredp-wrap-around-flag nil)) + (goto-char (if (< arg 0) (point-max) (point-min))) + (diredp-next-dirline arg opoint)) + (goto-char opoint) + (error "No more subdirectories")))) + +;;;###autoload +(defun diredp-prev-dirline (arg) ; Bound to `<' + "Goto ARGth previous directory file line." + (interactive (let ((narg (prefix-numeric-value current-prefix-arg))) + (when (and (boundp 'shift-select-mode) shift-select-mode) (handle-shift-selection)) ; Emacs 23+ + (list narg))) ; Equivalent to "^p" + (diredp-next-dirline (- arg))) + +;;;###autoload +(defun diredp-next-subdir (arg &optional no-error-if-not-found no-skip) ; Bound to `C-M-n' + "Go to the next subdirectory, regardless of level. +If ARG = 0 then go to this directory's header line. + +If `diredp-wrap-around-flag' is non-nil then wrap around if none is +found before the buffer end (buffer beginning, if ARG is negative). +Otherwise, raise an error or, if NO-ERROR-IF-NOT-FOUND is nil, return +nil. + +Non-nil NO-SKIP means do not move to end of header line, and return +the position moved to so far." + (interactive (let ((narg (prefix-numeric-value current-prefix-arg))) + (when (and (boundp 'shift-select-mode) shift-select-mode) (handle-shift-selection)) ; Emacs 23+ + (list narg))) ; Equivalent to "^p" + (let ((this-dir (dired-current-directory)) + pos index) + ;; `nth' with negative arg does not return nil but the first element + (setq index (if diredp-wrap-around-flag + (mod (- (dired-subdir-index this-dir) arg) (length dired-subdir-alist)) + (- (dired-subdir-index this-dir) arg)) + pos (and (>= index 0) (dired-get-subdir-min (nth index dired-subdir-alist)))) + (if pos + (progn (goto-char pos) + (or no-skip (skip-chars-forward "^\n\r")) + (point)) + (if no-error-if-not-found + nil ; Return nil if not found + (error "%s directory" (if (> arg 0) "Last" "First")))))) + +;;;###autoload +(defun diredp-prev-subdir (arg &optional no-error-if-not-found no-skip) ; Bound to `C-M-p' + "Go to the previous subdirectory, regardless of level. +When called interactively and not on a subdir line, go to this subdir's line. +Otherwise, this is a mirror image of `diredp-next-subdir'." + ;;(interactive "^p") + (interactive + (list (if current-prefix-arg + (let ((narg (prefix-numeric-value current-prefix-arg))) + (when (and (boundp 'shift-select-mode) shift-select-mode) (handle-shift-selection)) ; Emacs 23+ + narg) ; Equivalent to "^p" + ;; If on subdir start already then do not stay there. + (if (dired-get-subdir) 1 0)))) + (diredp-next-subdir (- arg) no-error-if-not-found no-skip)) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; 1. Test also ./ and ../, in addition to . and .., for error "Cannot operate on `.' or `..'". +;; 2. Hack for Emacs 20-22, to expand `~/...'. +;; +(defun dired-get-filename (&optional localp no-error-if-not-filep) + "In Dired, return name of file mentioned on this line. +Value returned normally includes the directory name. + +Optional arg LOCALP: + `no-dir' means do not include directory name in result. + `verbatim' means return the name exactly as it occurs in the buffer. + Any other non-nil value means construct the name relative to + `default-directory', which still might contain slashes if point is + in a subdirectory. + +Non-nil optional arg NO-ERROR-IF-NOT-FILEP means treat `.' and `..' as +regular filenames and return nil if there is no filename on this line. +Otherwise, an error occurs in these cases." + (let ((case-fold-search nil) + (already-absolute nil) + file p1 p2) + (save-excursion (when (setq p1 (dired-move-to-filename (not no-error-if-not-filep))) + (setq p2 (dired-move-to-end-of-filename no-error-if-not-filep)))) + ;; nil if no file on this line but `no-error-if-not-filep' is t: + (when (setq file (and p1 p2 (buffer-substring p1 p2))) + ;; Get rid of the mouse-face property that file names have. + (set-text-properties 0 (length file) nil file) + + ;; Unquote names quoted by `ls' or by `dired-insert-directory'. + ;; Prior to Emacs 23.3, this code was written using `read' (see commented code below), + ;; because that is faster than substituting \007 (4 chars) -> ^G (1 char) etc. in a loop. + ;; Unfortunately, that implementation required hacks such as dealing with filenames + ;; with quotation marks in their names. + (while (string-match (if (> emacs-major-version 21) + "\\(?:[^\\]\\|\\`\\)\\(\"\\)" ; Shy group: Emacs 22+. + "\\([^\\]\\|\\`\\)\\(\"\\)") + file) + (setq file (replace-match "\\\"" nil t file 1))) + + ;; $$$ This was the code for that unquoting prior to Emacs 23.3: + ;; (setq file (read (concat "\"" ; Some `ls -b' do not escape quotes. But GNU `ls' is OK. + ;; (or (dired-string-replace-match + ;; "\\([^\\]\\|\\`\\)\"" file "\\1\\\\\"" nil t) + ;; file) + ;; "\""))) + + ;; This sexp was added by Emacs 24, to fix bug #10469: + ;; Unescape any spaces escaped by `ls -b'. + ;; Other `-b' quotes, such as \t and \n, work transparently. + (when (dired-switches-escape-p dired-actual-switches) + (let ((start 0) + (rep "") + (shift -1)) + (when (eq localp 'verbatim) (setq rep "\\\\" + shift +1)) + (while (string-match "\\(\\\\\\) " file start) + (setq file (replace-match rep nil t file 1) + start (+ shift (match-end 0)))))) + + ;; $$$ This sexp was added by Emacs 23.3. + (when (memq system-type '(windows-nt ms-dos)) + (save-match-data + (let ((start 0)) + (while (string-match "\\\\" file start) + (aset file (match-beginning 0) ?/) + (setq start (match-end 0)))))) + + ;; $$$ This sexp was added by Emacs 23.3. + ;; Hence we don't need to worry about converting `\\' back to `\'. + (setq file (read (concat "\"" file "\""))) + + ;; Above `read' returns a unibyte string if FILE contains eight-bit-control/graphic chars. + (when (and (fboundp 'string-to-multibyte) ; Emacs 22 + enable-multibyte-characters + (not (multibyte-string-p file))) + (setq file (string-to-multibyte file)))) + (and file + (file-name-absolute-p file) + ;; A relative file name can start with ~. Do not treat it as absolute in this context. + (not (eq (aref file 0) ?~)) + (setq already-absolute t)) + (cond ((null file) nil) + ((eq localp 'verbatim) file) + ;; This is the essential `Dired+' change: Added ./ and ../, not just . and .. + ((and (not no-error-if-not-filep) (member file '("." ".." "./" "../"))) + (error "Cannot operate on `.' or `..'")) + ((and (eq localp 'no-dir) already-absolute) + (file-name-nondirectory file)) + (already-absolute + (let ((handler (find-file-name-handler file nil))) + ;; check for safe-magic property so that we won't + ;; put /: for names that don't really need them. + ;; For instance, .gz files when auto-compression-mode is on. + (if (and handler (not (get handler 'safe-magic))) + (concat "/:" file) + file))) + ((eq localp 'no-dir) file) + ((equal (dired-current-directory) "/") + (setq file (concat (dired-current-directory localp) file)) + (let ((handler (find-file-name-handler file nil))) + ;; check for safe-magic property so that we won't + ;; put /: for names that don't really need them. + ;; For instance, .gz files when auto-compression-mode is on. + (if (and handler (not (get handler 'safe-magic))) + (concat "/:" file) + file))) + ;; Ugly hack for Emacs < 23, for which `ls-lisp-insert-directory' can insert a subdir + ;; using `~/...'. Expand `~/' for return value. + ((and (< emacs-major-version 23) file (file-name-absolute-p file) + (eq (aref file 0) ?~)) + (expand-file-name file)) + (t + (concat (dired-current-directory localp) file))))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; 1. Fixes Emacs bug #7126: Did not work with arbitrary file list (cons arg to `dired'). +;; 2. Remove `/' from directory name before comparing with BASE. +;; +(when (< emacs-major-version 24) + (defun dired-goto-file (file) ; Bound to `j' + "Go to line describing file FILE in this Dired buffer. +FILE must be an absolute file name. +Return buffer position on success, else nil." + ;; Loses if FILE contains control chars like "\007" for which `ls' inserts "?" or "\\007" + ;; into the buffer, so we won't find it in the buffer. + (interactive (prog1 ; Let push-mark display its message + (list (expand-file-name (read-file-name "Goto file: " (dired-current-directory)))) + (push-mark))) + (unless (file-name-absolute-p file) (error "File name `%s' is not absolute" file)) + (setq file (directory-file-name file)) ; does no harm if no directory + (let* ((case-fold-search nil) + (dir (file-name-directory file)) + (found nil)) + ;; `Dired+': Added this sexp. + (save-excursion + (goto-char (point-min)) + (let ((search-string (replace-regexp-in-string "\^m" "\\^m" file nil t)) + (here nil)) + (setq search-string (replace-regexp-in-string "\\\\" "\\\\" search-string nil t)) + + ;; Escape whitespace. Added per Emacs 24 addition in `unless' code below: + (when (and (dired-switches-escape-p dired-actual-switches) + (diredp-string-match-p "[ \t\n]" search-string)) + ;; FIXME: fix this for all possible file names (embedded control chars etc). + ;; Need to escape everything that `ls -b' escapes. + (setq search-string (replace-regexp-in-string " " "\\ " search-string nil t) + search-string (replace-regexp-in-string "\t" "\\t" search-string nil t) + search-string (replace-regexp-in-string "\n" "\\n" search-string nil t))) + + ;; Use HERE to ensure we do not keep searching for a directory entry. + (while (and (not (eobp)) (not found) (not (equal here (point)))) + (setq here (point)) + (if (search-forward (concat " " search-string) nil 'NO-ERROR) + ;; Must move to filename since an (actually correct) match could have been + ;; elsewhere on the line (e.g. "-" would match somewhere in permission bits). + (setq found (dired-move-to-filename)) + ;; If this isn't the right line, move forward to avoid trying this line again. + (forward-line 1))))) + + (unless found + (save-excursion + ;; The difficulty here is to get the result of `dired-goto-subdir' without really + ;; calling it, if we don't have any subdirs. + (when (if (string= dir (expand-file-name default-directory)) + (goto-char (point-min)) + (and (cdr dired-subdir-alist) (dired-goto-subdir dir))) + (let ((base (file-name-nondirectory file)) + (boundary (dired-subdir-max)) + search-string) + (setq search-string (replace-regexp-in-string "\^m" "\\^m" base nil t) + search-string (replace-regexp-in-string "\\\\" "\\\\" search-string nil t)) + + ;; Escape whitespace. Sexp added by Emacs 24: + (when (and (dired-switches-escape-p dired-actual-switches) + (diredp-string-match-p "[ \t\n]" search-string)) + ;; FIXME: fix this for all possible file names (embedded control chars etc). + ;; Need to escape everything that `ls -b' escapes. + (setq search-string (replace-regexp-in-string " " "\\ " search-string nil t) + search-string (replace-regexp-in-string "\t" "\\t" search-string nil t) + search-string (replace-regexp-in-string "\n" "\\n" search-string nil t))) + (while (and (not found) + ;; Filenames are preceded by SPC. This makes the search faster + ;; (e.g. for the filename "-"!). + (search-forward (concat " " search-string) boundary 'move)) + ;; `Dired+': Remove `/' from filename, then compare with BASE. + ;; Match could have BASE just as initial substring or + ;; or in permission bits or date or not be a proper filename at all. + (if (and (dired-get-filename 'no-dir t) + (equal base (directory-file-name (dired-get-filename 'no-dir t)))) + ;; Must move to filename since an (actually correct) match could have been + ;; elsewhere on the line (e.g. "-" would match somewhere in permission bits). + (setq found (dired-move-to-filename)) + ;; If this is not the right line, move forward to avoid trying this line again. + (forward-line 1))))))) + (and found (goto-char found))))) ; Return buffer position, or nil if not found. + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; If destination is in a hidden dir listing, open that listing and move to destination in it. +;; +(unless (< emacs-major-version 24) + (defun dired-goto-file (file) + "Go to line describing file FILE in this Dired buffer. +FILE must be an absolute file name. +Return buffer position on success, else nil." + ;; Loses if FILE contains control chars like "\007" for which `ls' inserts "?" or "\\007" + ;; into the buffer, so we won't find it in the buffer. + (interactive (prog1 (list (expand-file-name (read-file-name "Goto file: " (dired-current-directory)))) + (push-mark))) ; Let push-mark display its message. + (unless (file-name-absolute-p file) (error "File name `%s' is not absolute" file)) + (setq file (directory-file-name file)) ; Does no harm if not a directory + (let* ((case-fold-search nil) + (dir (file-name-directory file)) + (found + (or + ;; First, look for a listing under the absolute name. + (save-excursion (goto-char (point-min)) (dired-goto-file-1 file file (point-max))) + ;; Else look for it as a relative name. The difficulty is to get the result + ;; of `dired-goto-subdir' without calling it, if we don't have any subdirs. + (save-excursion + (when (if (string= dir (expand-file-name default-directory)) + (goto-char (point-min)) + (and (cdr dired-subdir-alist) (dired-goto-subdir dir))) + (when (dired-subdir-hidden-p (dired-current-directory)) + (diredp-hide-subdir-nomove 1)) ; Open hidden parent directory. + (dired-goto-file-1 (file-name-nondirectory file) file (dired-subdir-max))))))) + (and found (goto-char found))))) ; Return buffer position, or nil if not found. + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; 1. Display a message to warn that flagged, not marked, files will be deleted. +;; 2. Use `diredp-internal-do-deletions', so it works with all Emacs versions. +;; +;;;###autoload +(defun dired-do-flagged-delete (&optional no-msg) ; Bound to `x' + "In Dired, delete the files flagged for deletion. +NOTE: This deletes flagged, not marked, files. +If arg NO-MSG is non-nil, no message is displayed. + +User option `dired-recursive-deletes' controls whether deletion of +non-empty directories is allowed." + (interactive) + (unless no-msg + (ding) + (message "NOTE: Deletion of files flagged `%c' (not those marked `%c')" + dired-del-marker dired-marker-char) + ;; Too slow/annoying, but without it the message is never seen: (sit-for 2) + ) + (let* ((dired-marker-char dired-del-marker) + (regexp (dired-marker-regexp)) + (case-fold-search nil)) + (if (save-excursion (goto-char (point-min)) (re-search-forward regexp nil t)) + (diredp-internal-do-deletions + ;; This cannot move point since last arg is nil. + (dired-map-over-marks (cons (dired-get-filename) (point)) nil) + nil + 'USE-TRASH-CAN) ; This arg is for Emacs 24+ only. + (unless no-msg (message "(No deletions requested.)"))))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; 1. Display a message to warn that marked, not flagged, files will be deleted. +;; 2. Use `diredp-internal-do-deletions', so it works with all Emacs versions. +;; +;;;###autoload +(defun dired-do-delete (&optional arg) ; Bound to `D' + "Delete all marked (or next ARG) files. +NOTE: This deletes marked, not flagged, files. +`dired-recursive-deletes' controls whether deletion of +non-empty directories is allowed." + (interactive "P") + ;; This is more consistent with the file-marking feature than + ;; `dired-do-flagged-delete'. But it can be confusing to the user, + ;; especially since this is usually bound to `D', which is also the + ;; `dired-del-marker'. So offer this warning message: + (unless arg + (ding) + (message "NOTE: Deletion of files marked `%c' (not those flagged `%c')." + dired-marker-char dired-del-marker)) + (diredp-internal-do-deletions + ;; This can move point if ARG is an integer. + (dired-map-over-marks (cons (dired-get-filename) (point)) arg) + arg + 'USE-TRASH-CAN)) ; This arg is for Emacs 24+ only. + +(defun diredp-internal-do-deletions (file-alist arg &optional trash) + "`dired-internal-do-deletions', but for any Emacs version. +FILE-ALIST is an alist of files to delete, with their buffer positions. +ARG is the prefix arg. Filenames are absolute. +Non-nil TRASH means use the trash can." + ;; \(car FILE-ALIST) *must* be the *last* (bottommost) file in the dired + ;; buffer. That way as changes are made in the buffer they do not shift + ;; the lines still to be changed, so the (point) values in FILE-ALIST + ;; stay valid. Also, for subdirs in natural order, a subdir's files are + ;; deleted before the subdir itself - the other way around would not work." + (setq file-alist (delq nil file-alist)) ; nils could come from `dired-map-over-marks'. + (if (> emacs-major-version 23) + (dired-internal-do-deletions file-alist arg trash) + (dired-internal-do-deletions file-alist arg))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; Put window point at bob. Fixes bug #12281. +;; +(when (and (> emacs-major-version 22) (or (< emacs-major-version 24) + (and (= emacs-major-version 24) (= emacs-minor-version 1)))) + (defun dired-pop-to-buffer (buf) + "Pop up buffer BUF in a way suitable for Dired." + (let ((split-window-preferred-function + (lambda (window) + (or (and (let ((split-height-threshold 0)) (window-splittable-p (selected-window))) + ;; Try to split the selected window vertically if that's possible. (Bug#1806) + (if (fboundp 'split-window-below) (split-window-below) (split-window-vertically))) + (split-window-sensibly window)))) + pop-up-frames) + (pop-to-buffer (get-buffer-create buf))) + (set-window-start (selected-window) (point-min)) + (when dired-shrink-to-fit + ;; Try to not delete window when we want to display less than `window-min-height' lines. + (fit-window-to-buffer (get-buffer-window buf) nil 1)))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; 1. Delete the window or frame popped up, afterward, and bury its buffer. +;; Fixes Emacs bug #7533. +;; +;; 2, If buffer is shown in a separate frame, do not show a menu bar for that frame. +;; +(defun dired-mark-pop-up (buffer-or-name op-symbol files function &rest args) + "Return FUNCTION's result on ARGS after showing which files are marked. +Displays the file names in a buffer named BUFFER-OR-NAME, the default +name being \" *Marked Files*\". The buffer is not shown if there is +just one file, `dired-no-confirm' is t, or OP-SYMBOL is a member of +the list in `dired-no-confirm'. Uses function `dired-pop-to-buffer' +to show the buffer. + +The window is not shown if there is just one file, `dired-no-confirm' +is `t', or OP-SYMBOL is a member of `dired-no-confirm'. + +FILES is the list of marked files. It can also be (t FILENAME) +in the case of one marked file, to distinguish that from using +just the current file. + +FUNCTION should not manipulate the files. It should just read input +\(an argument or confirmation)." + (unless buffer-or-name (setq buffer-or-name " *Marked Files*")) + (let (result) + (if (or (eq dired-no-confirm t) + (memq op-symbol dired-no-confirm) + ;; If FILES defaulted to the current line's file. + (= (length files) 1)) + (setq result (apply function args)) + (with-current-buffer (get-buffer-create buffer-or-name) + (erase-buffer) + ;; Handle (t FILE) just like (FILE), here. That value is used (only in some cases), + ;; to mean just one file that was marked, rather than the current-line file. + (dired-format-columns-of-files (if (eq (car files) t) (cdr files) files)) + (remove-text-properties (point-min) (point-max) + '(mouse-face nil help-echo nil))) + (unwind-protect + (save-window-excursion + ;; Do not show menu bar, if buffer is popped up in a separate frame. + (let ((special-display-frame-alist (cons '(menu-bar-lines . 0) + special-display-frame-alist)) + (default-frame-alist (cons '(menu-bar-lines . 0) + default-frame-alist))) + (dired-pop-to-buffer buffer-or-name) + ;; Work around Emacs 22 bug in `dired-pop-to-buffer', which can exit with Dired buffer current. + (set-buffer buffer-or-name) + (goto-char (point-min))) + (setq result (apply function args))) + (save-excursion + (condition-case nil ; Ignore error if user already deleted window. + (progn (select-window (get-buffer-window buffer-or-name 0)) + (if (one-window-p) (delete-frame) (delete-window))) + (error nil))) + (bury-buffer buffer-or-name))) + result)) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; 1. Prefix arg has more possibilities. +;; 2, Added optional arg LOCALP, so you can mark/unmark matching different file-name forms. +;; 3. Push REGEXP onto `regexp-search-ring'. +;; +;;;###autoload +(defun dired-mark-files-regexp (regexp &optional marker-char localp) + "Mark all file names matching REGEXP for use in later commands. +`.' and `..' are never marked or unmarked by this command. + +Whether to mark or unmark, and what form of file name to match, are +governed by the prefix argument. For this, a plain (`C-u') or a +double-plain (`C-u C-u') prefix arg is considered only as such - it is +not considered numerically. + +Whether to mark or unmark: + + - No prefix arg, a positive arg, or a negative arg means mark. + + - Plain (`C-u'), double-plain (`C-u C-u'), or zero (e.g. `M-0' means + unmark. + +The form of a file name used for matching: + + - No prefix arg (to mark) or a plain prefix arg (`C-u', to unmark) + means use the relative file name (no directory part). + + - A negative arg (e.g. `M--', to mark) or a zero arg (e.g. `M-0', to + unmark) means use the absolute file name, that is, including all + directory components. + + - A positive arg (e.g. `M-+', to mark) or a double plain arg (`C-u + C-u', to unmark) means construct the name relative to + `default-directory'. For an entry in an inserted subdir listing, + this means prefix the relative file name (no directory part) with + the subdir name relative to `default-directory'. + +Note that the default matching behavior of this command is different +for Dired+ than it is for vanilla Emacs. Using a positive prefix arg +or a double plain prefix arg (`C-u C-u') gives you the same behavior +as vanilla Emacs (marking or unmarking, respectively): matching +against names that are relative to the `default-directory'. + +What Dired+ offers in addition is the possibility to match against +names that are relative (have no directory part - no prefix arg or +`C-u' to mark and unmark, respectively) or absolute (`M--' or `M-0', +respectively). The default behavior uses relative names because this +is likely to be the more common use case. But matching against +absolute names gives you more flexibility. + +REGEXP is an Emacs regexp, not a shell wildcard. Thus, use `\\.o$' +for object files--just `.o' might mark more than you might expect. + +REGEXP is added to `regexp-search-ring', for regexp search. + +Non-interactively: + MARKER-CHAR is the marker character - used for `dired-marker-char'. + LOCALP is passed to `dired-get-filename'. It determines the form of + filename that is matched against REGEXP." + (interactive (let* ((raw current-prefix-arg) + (C-u (and (consp raw) (= 4 (car raw)))) + (C-u-C-u (and (consp raw) (= 16 (car raw)))) + (num (and raw (prefix-numeric-value raw)))) + (list (diredp-read-regexp (concat (if (or (consp raw) (and num (zerop num))) + "UNmark" + "Mark") + " files (regexp): ")) + (and raw (or C-u C-u-C-u (zerop num)) ?\040) + (cond ((or (not raw) C-u) t) ; none, `C-u' + ((> num 0) nil) ; `M-+', `C-u C-u' + (t 'no-dir))))) ; `M--', `M-0' + (add-to-list 'regexp-search-ring regexp) ; Add REGEXP to `regexp-search-ring'. + (let ((dired-marker-char (or marker-char dired-marker-char))) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) + (not (eolp)) ; Empty line + (let ((fn (dired-get-filename localp t))) + (and fn (diredp-string-match-p regexp fn)))) + "file"))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; Use `diredp-mark-if', not `dired-mark-if'. +;; +;;;###autoload +(defun dired-mark-files-containing-regexp (regexp &optional marker-char) + "Mark files with contents containing a REGEXP match. +A prefix argument means unmark them instead. +`.' and `..' are never marked. + +If a file is visited in a buffer and `dired-always-read-filesystem' is +nil, this looks in the buffer without revisiting the file, so the +results might be inconsistent with the file on disk if its contents +have changed since it was last visited." + (interactive + (list (diredp-read-regexp (concat (if current-prefix-arg "Unmark" "Mark") " files containing (regexp): ") + nil 'dired-regexp-history) + (and current-prefix-arg ?\040))) + (let ((dired-marker-char (or marker-char dired-marker-char))) + (diredp-mark-if (and (not (diredp-looking-at-p dired-re-dot)) + (not (eolp)) + (let ((fname (dired-get-filename nil t))) + (when (and fname (file-readable-p fname) (not (file-directory-p fname))) + (let ((prebuf (get-file-buffer fname))) + (message "Checking %s" fname) + ;; For now, do it inside Emacs. Grep might be better if there are lots of files. + (if (and prebuf (or (not (boundp 'dired-always-read-filesystem)) + (not dired-always-read-filesystem))) ; Emacs 26+ + (with-current-buffer prebuf + (save-excursion (goto-char (point-min)) (re-search-forward regexp nil t))) + (with-temp-buffer + (insert-file-contents fname) + (goto-char (point-min)) + (re-search-forward regexp nil t))))))) + "file"))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; Use `diredp-mark-if', not `dired-mark-if'. +;; +;;;###autoload +(defun dired-mark-symlinks (unflag-p) + "Mark all symbolic links. +With prefix argument, unmark or unflag all those files." + (interactive "P") + (let ((dired-marker-char (if unflag-p ?\040 dired-marker-char))) + (diredp-mark-if (diredp-looking-at-p dired-re-sym) "symbolic link"))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; Use `diredp-mark-if', not `dired-mark-if'. +;; +;;;###autoload +(defun dired-mark-directories (unflag-p) + "Mark all directory file lines except `.' and `..'. +With prefix argument, unmark or unflag the files instead." + (interactive "P") + (let ((dired-marker-char (if unflag-p ?\040 dired-marker-char))) + (diredp-mark-if (and (diredp-looking-at-p dired-re-dir) (not (diredp-looking-at-p dired-re-dot))) + "directory" "directories"))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; Use `diredp-mark-if', not `dired-mark-if'. +;; +;;;###autoload +(defun dired-mark-executables (unflag-p) + "Mark all executable files. +With prefix argument, unmark or unflag the files instead." + (interactive "P") + (let ((dired-marker-char (if unflag-p ?\040 dired-marker-char))) + (diredp-mark-if (diredp-looking-at-p dired-re-exe) "executable file"))) + + +;; REPLACE ORIGINAL in `dired.el': +;; +;; Use `diredp-mark-if', not `dired-mark-if'. +;; +;;;###autoload +(defun dired-flag-auto-save-files (&optional unflag-p) + "Flag for deletion files whose names suggest they are auto save files. +A prefix argument says to unmark or unflag the files instead." + (interactive "P") + (let ((dired-marker-char (if unflag-p ?\040 dired-del-marker))) + (diredp-mark-if + ;; It is less than general to check for # here, but it's the only way this runs fast enough. + (and (save-excursion (end-of-line) + (or (eq (preceding-char) ?#) + ;; Handle executables in case of -F option. Need not worry about the other kinds + ;; of markings that -F makes, since they won't appear on real auto-save files. + (and (eq (preceding-char) ?*) + (progn (forward-char -1) (eq (preceding-char) ?#))))) + (not (diredp-looking-at-p dired-re-dir)) + (let ((fname (dired-get-filename t t))) + (and fname (auto-save-file-name-p (file-name-nondirectory fname))))) + "auto-save file"))) + +;;;###autoload +(defun diredp-capitalize (&optional arg) ; Bound to `% c' + "Rename all marked (or next ARG) files by capitalizing them. +Makes the first char of the name uppercase and the others lowercase." + (interactive "P") + (dired-rename-non-directory #'capitalize "Rename by capitalizing:" arg)) + +;; This is more useful than a single-file version of `dired-do-delete'. +;;;###autoload +(defun diredp-delete-this-file (&optional use-trash-can) ; Bound to `C-k', `delete' + "In Dired, delete the file on the cursor line, upon confirmation. +This uses `delete-file'. +If the file is a symlink, remove the symlink. If the file has +multiple names, it continues to exist with the other names. + +For Emacs 24 and later, a prefix arg means that if +`delete-by-moving-to-trash' is non-nil then trash the file instead of +deleting it." + (interactive "P") + (let ((file (dired-get-filename))) + (if (not (yes-or-no-p (format "%s file `%s'? " (if (and use-trash-can delete-by-moving-to-trash) + "Trash" + "Permanently delete") + file))) + (message "OK - canceled") + (if (> emacs-major-version 23) (delete-file file use-trash-can) (delete-file file)) + (revert-buffer)))) + +;;; Versions of `dired-do-*' commands for just this line's file. +;;;###autoload +(defun diredp-capitalize-this-file () ; Bound to `M-c' + "In Dired, rename the file on the cursor line by capitalizing it. +Makes the first char of the name uppercase and the others lowercase." + (interactive) (diredp-capitalize 1)) + +;;;###autoload +(defun diredp-downcase-this-file () ; Bound to `M-l' + "In Dired, rename the file on the cursor line to lower case." + (interactive) (dired-downcase 1)) + +;;;###autoload +(defun diredp-upcase-this-file () ; Bound to `M-u' + "In Dired, rename the file on the cursor line to upper case." + (interactive) (dired-upcase 1)) + +;;;###autoload +(defun diredp-rename-this-file () ; Bound to `r' + "In Dired, rename the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (dired-do-rename 1))) + +(when (fboundp 'epa-dired-do-encrypt) ; Emacs 23+ + (defun diredp-decrypt-this-file () + "In Dired, decrypt the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (epa-dired-do-decrypt 1))) + + (defun diredp-encrypt-this-file () + "In Dired, encrypt the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (epa-dired-do-encrypt 1))) + + (defun diredp-verify-this-file () + "In Dired, verify the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (epa-dired-do-verify 1))) + + (defun diredp-sign-this-file () + "In Dired, sign the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (epa-dired-do-sign 1)))) + +;;;###autoload +(defun diredp-copy-this-file () ; Not bound + "In Dired, copy the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (dired-do-copy 1))) + +;;;###autoload +(defun diredp-relsymlink-this-file () ; Bound to `y' + "In Dired, make a relative symbolic link to file on cursor line." + (interactive) + (let ((use-file-dialog nil)) (dired-do-relsymlink 1))) + +;;;###autoload +(defun diredp-symlink-this-file () ; Not bound + "In Dired, make a symbolic link to the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (dired-do-symlink 1))) + +;;;###autoload +(defun diredp-hardlink-this-file () ; Not bound + "In Dired, add a name (hard link) to the file on the cursor line." + (interactive) + (let ((use-file-dialog nil)) (dired-do-hardlink 1))) + +;;;###autoload +(defun diredp-print-this-file () ; Bound to `M-p' + "In Dired, print the file on the cursor line." + (interactive) (dired-do-print 1)) + +;;;###autoload +(defun diredp-grep-this-file () ; Not bound + "In Dired, grep the file on the cursor line." + (interactive) + (unless (and grep-command (or (< emacs-major-version 22) + (not grep-use-null-device) + (eq grep-use-null-device t))) + (grep-compute-defaults)) + (grep (diredp-do-grep-1 (list (dired-get-filename t))))) + +;;;###autoload +(defun diredp-compress-this-file () ; Bound to `z' + "In Dired, compress or uncompress the file on the cursor line." + (interactive) (dired-do-compress 1)) + +;;;###autoload +(defun diredp-async-shell-command-this-file (command filelist) ; Not bound + "Run a shell COMMAND asynchronously on the file on the Dired cursor line. +Like `diredp-shell-command-this-file', but adds `&' at the end of +COMMAND to execute it asynchronously. The command output appears in +buffer `*Async Shell Command*'." + (interactive (list (dired-read-shell-command (concat "& on " "%s: ") 1 (list (dired-get-filename t))) + (list (dired-get-filename t)))) + (unless (diredp-string-match-p "&[ \t]*\\'" command) (setq command (concat command " &"))) + (dired-do-shell-command command 1 filelist)) + +;;;###autoload +(defun diredp-shell-command-this-file (command filelist) ; Not bound + "In Dired, run a shell COMMAND on the file on the cursor line." + (interactive (list (dired-read-shell-command (concat "! on " "%s: ") 1 (list (dired-get-filename t))) + (list (dired-get-filename t)))) + (dired-do-shell-command command 1 filelist)) + +;;;###autoload +(defun diredp-bookmark-this-file (&optional prefix) ; Bound to `C-B' (`C-S-b') + "In Dired, bookmark the file on the cursor line. +See `diredp-do-bookmark'." + (interactive (progn (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: "))))) + (diredp-do-bookmark prefix 1)) + +;;;###autoload +(defun diredp-tag-this-file (tags &optional prefix) ; Bound to `T +' + "In Dired, add some tags to the file on the cursor line. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (bmkp-read-tags-completing) + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: "))))) + (diredp-do-tag tags prefix 1)) + +;;;###autoload +(defun diredp-untag-this-file (tags &optional prefix arg) ; Bound to `T -' + "In Dired, remove some tags from the file on the cursor line. +With a prefix arg, remove all tags from the file. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (let* ((pref (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: "))) + (bmk (bmkp-get-autofile-bookmark (dired-get-filename) nil pref)) + (btgs (and bmk (bmkp-get-tags bmk)))) + (unless btgs (error "File has no tags to remove")) + (list (if current-prefix-arg btgs (bmkp-read-tags-completing btgs)) + pref + current-prefix-arg)))) + (diredp-do-untag tags prefix 1)) + +;;;###autoload +(defun diredp-remove-all-tags-this-file (&optional prefix msgp) ; Bound to `T 0' + "In Dired, remove all tags from this file. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + 'MSG))) + (bookmark-maybe-load-default-file) + (diredp-do-remove-all-tags prefix 1)) + +;;;###autoload +(defun diredp-paste-add-tags-this-file (&optional prefix msgp) ; Bound to `T p', `T C-y' + "In Dired, add previously copied tags to this file. +See `diredp-paste-add-tags'. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + 'MSG))) + (bookmark-maybe-load-default-file) + (diredp-do-paste-add-tags prefix 1)) + +;;;###autoload +(defun diredp-paste-replace-tags-this-file (&optional prefix msgp) ; Bound to `T q' + "In Dired, replace tags for this file with previously copied tags. +See `diredp-paste-replace-tags'. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + 'MSG))) + (bookmark-maybe-load-default-file) + (diredp-do-paste-add-tags prefix 1)) + +;;;###autoload +(defun diredp-set-tag-value-this-file (tag value &optional prefix msgp) ; Bound to `T v' + "In Dired, Set value of TAG to VALUE for this file. +See `diredp-set-tag-value'. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (bmkp-read-tag-completing) + (read (read-string "Value: ")) + (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + 'MSG))) + (bookmark-maybe-load-default-file) + (diredp-do-set-tag-value tag value prefix 1)) + +;;;###autoload +(defun diredp-copy-tags-this-file (&optional prefix msgp) ; Bound to `T c', `T M-w' + "In Dired, copy the tags from this file, so you can paste them to another. +See `diredp-copy-tags'. +You need library `bookmark+.el' to use this command." + (interactive (progn (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (list (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")) + 'MSG))) + (bookmark-maybe-load-default-file) + (let ((bmk (bmkp-get-autofile-bookmark (dired-get-filename) nil prefix))) + (and bmk (bmkp-copy-tags bmk msgp)))) + +;;;###autoload +(defun diredp-mouse-copy-tags (event) ; Not bound + "In Dired, copy the tags from this file, so you can paste them to another. +You need library `bookmark+.el' to use this command." + (interactive "e") + (let ((mouse-pos (event-start event)) + (dired-no-confirm t) + (prefix (and diredp-prompt-for-bookmark-prefix-flag + (read-string "Prefix for bookmark name: ")))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (diredp-copy-tags-this-file prefix 'MSG)) + (diredp-previous-line 1)) + +(when (fboundp 'describe-file) ; In `help-fns+.el' or `help+20.el'. + (defun diredp-describe-file (&optional internal-form-p) ; Bound to `C-h RET', `C-h C-RET' + "In Dired, describe this file or directory. +You need library `help-fns+.el' to use this command. +If the file has an autofile bookmark and you use library `Bookmark+', +then show also the bookmark information (tags etc.). In this case, a +prefix arg shows the internal form of the bookmark." + (interactive "P") + (describe-file (dired-get-filename nil t) internal-form-p)) + + (defun diredp-mouse-describe-file (event &optional internal-form-p) ; Not bound + "Describe the clicked file. +You need library `help-fns+.el' to use this command. +If the file has an autofile bookmark and you use library `Bookmark+', +then show also the bookmark information (tags etc.). In this case, a +prefix arg shows the internal form of the bookmark." + (interactive "e\nP") + (let (file) + (with-current-buffer (window-buffer (posn-window (event-end event))) + (save-excursion (goto-char (posn-point (event-end event))) + (setq file (dired-get-filename nil t)))) + (describe-file file internal-form-p)))) + +;; Define these even if `Bookmark+' is not loaded. +;;;###autoload +(defalias 'diredp-show-metadata 'diredp-describe-autofile) +;;;###autoload +(defun diredp-describe-autofile (&optional internal-form-p) + "Show the metadata for the file of the current line. +The file must name an autofile bookmark. The metadata is the bookmark +information. + +With a prefix argument, show the internal definition of the bookmark. + +You need library `bookmark+.el' for this command." + (interactive "P") + (diredp-ensure-bookmark+) + (diredp-ensure-mode) + (let ((bmk (save-match-data + (bmkp-get-autofile-bookmark (dired-get-filename nil t))))) + (unless bmk (error "Not on an autofile bookmark")) + (save-selected-window (if internal-form-p + (bmkp-describe-bookmark-internals bmk) + (bmkp-describe-bookmark bmk))))) + +(defun diredp-mouse-describe-autofile (event &optional internal-form-p) ; Not bound + "Show the metadata for the file whose name you click. +The file must name an autofile bookmark. The metadata is the bookmark +information. + +With a prefix argument, show the internal definition of the bookmark. + +You need library `bookmark+.el' for this command." + (interactive "e\nP") + (diredp-ensure-bookmark+) + (let (file) + (with-current-buffer (window-buffer (posn-window (event-end event))) + (diredp-ensure-mode) + (save-excursion (goto-char (posn-point (event-end event))) + (setq file (dired-get-filename nil t)))) + (let ((bmk (save-match-data (bmkp-get-autofile-bookmark file)))) + (unless bmk (error "Not an autofile bookmark")) + (save-selected-window (if internal-form-p + (bmkp-describe-bookmark-internals bmk) + (bmkp-describe-bookmark bmk)))))) + +;;;###autoload +(defalias 'diredp-show-metadata-for-marked 'diredp-describe-marked-autofiles) +;;;###autoload +(defun diredp-describe-marked-autofiles (&optional internal-form-p interactivep details) + "Show metadata for the marked files. +If no file is marked, describe ALL autofiles in this directory. +With a prefix argument, show the internal (Lisp) form of the metadata. +When invoked interactively, raise an error if no files are marked. +You need library `bookmark+.el' for this command. + +When called from Lisp, optional arg DETAILS is passed to +`diredp-get-files'." + (interactive (list current-prefix-arg t diredp-list-file-attributes)) + (diredp-ensure-bookmark+) + (let ((help-xref-following nil)) + (help-setup-xref (list `(lambda (_buf) + (with-current-buffer ,(current-buffer) (diredp-describe-marked-autofiles))) + internal-form-p) + (if (or (> emacs-major-version 23) + (and (= emacs-major-version 23) (> emacs-minor-version 1))) + (called-interactively-p 'interactive) + (interactive-p)))) + (diredp-with-help-window "*Help*" + (let ((marked (dired-get-marked-files nil nil nil 'DISTINGUISH-ONE-MARKED interactivep))) + (unless (cdr marked) + (message "Describing ALL autofiles here (none are marked)...") + (setq marked (diredp-get-files 'IGNORE-MARKS-P nil nil nil nil details))) + (if (eq t (car marked)) + (diredp-describe-autofile internal-form-p) + (dolist (bmk (delq nil (mapcar #'bmkp-get-autofile-bookmark marked))) + (if internal-form-p + (let* ((bname (bmkp-bookmark-name-from-record bmk)) + (help-text (format "%s\n%s\n\n%s" + bname (make-string (length bname) ?-) (pp-to-string bmk)))) + (princ help-text) (terpri)) + (princ (bmkp-bookmark-description bmk)) (terpri))))))) + +;;;###autoload +(defun diredp-byte-compile-this-file () ; Bound to `b' + "In Dired, byte compile the (Lisp source) file on the cursor line." + (interactive) (dired-do-byte-compile 1)) + +;;;###autoload +(defun diredp-load-this-file () ; Not bound + "In Dired, load the file on the cursor line." + (interactive) (dired-do-load 1)) + +;;;###autoload +(defun diredp-chmod-this-file () ; Bound to `M-m' + "In Dired, change the mode of the file on the cursor line." + (interactive) (dired-do-chmod 1)) + +(unless (memq system-type '(windows-nt ms-dos)) + (defun diredp-chgrp-this-file () ; Not bound + "In Dired, change the group of the file on the cursor line." + (interactive) (dired-do-chgrp 1))) + +(unless (memq system-type '(windows-nt ms-dos)) + (defun diredp-chown-this-file () ; Not bound + "In Dired, change the owner of the file on the cursor line." + (interactive) (dired-do-chown 1))) + +(when (fboundp 'dired-do-touch) + (defun diredp-touch-this-file () ; Not bound + "In Dired, `touch' (change the timestamp of) the file on the cursor line." + (interactive) (dired-do-touch 1))) + + +;; REPLACE ORIGINAL in `dired-x.el'. +;; +;; 1. Variable (symbol) `s' -> `blks'. +;; 2. Fixes to remove leading space from `uid' and allow `.' in `gid'. +;; 3. Cleaned up doc string and code a bit. +;; +;;;###autoload +(defun dired-mark-sexp (predicate &optional unmark-p) ; Bound to `M-(', `* (' + "Mark files for which PREDICATE returns non-nil. +With a prefix arg, unmark or unflag those files instead. + +PREDICATE is a lisp sexp that can refer to the following symbols as +variables: + + `mode' [string] file permission bits, e.g. \"-rw-r--r--\" + `nlink' [integer] number of links to file + `size' [integer] file size in bytes + `uid' [string] owner + `gid' [string] group (If the gid is not displayed by `ls', + this will still be set (to the same as uid)) + `time' [string] the time that `ls' displays, e.g. \"Feb 12 14:17\" + `name' [string] the name of the file + `sym' [string] if file is a symbolic link, the linked-to name, + else \"\" + `inode' [integer] the inode of the file (only for `ls -i' output) + `blks' [integer] the size of the file for `ls -s' output + (ususally in blocks or, with `-k', in Kbytes) +Examples: + Mark zero-length files: `(equal 0 size)' + Mark files last modified on Feb 2: `(string-match \"Feb 2\" time)' + Mark uncompiled Emacs Lisp files (`.el' file without a `.elc' file): + First, Dired just the source files: `dired *.el'. + Then, use \\[dired-mark-sexp] with this sexp: + (not (file-exists-p (concat name \"c\"))) + +There's an ambiguity when a single integer not followed by a unit +prefix precedes the file mode: It is then parsed as inode number +and not as block size (this always works for GNU coreutils ls). + +Another limitation is that the uid field is needed for the +function to work correctly. In particular, the field is not +present for some values of `ls-lisp-emulation'. + +This function operates only on the Dired buffer content. It does not +refer at all to the underlying file system. Contrast this with +`find-dired', which might be preferable for the task at hand." + ;; Using `sym' = "", instead of nil, for non-linked files avoids the trap of + ;; (string-match "foo" sym) into which a user would soon fall. + ;; Use `equal' instead of `=' in the example, as it works on integers and strings. + (interactive "xMark if (vars: inode,blks,mode,nlink,uid,gid,size,time,name,sym): \nP") + (message "%s" predicate) + (let ((dired-marker-char (if unmark-p ?\040 dired-marker-char)) + (inode nil) + (blks ()) + mode nlink uid gid size time name sym) + (diredp-mark-if + (save-excursion + (and + ;; Sets vars INODE BLKS MODE NLINK UID GID SIZE TIME NAME and SYM + ;; according to current file line. Returns `t' for success, nil if + ;; there is no file line. Upon success, these vars are set, to either + ;; nil or the appropriate value, so they need not be initialized. + ;; Moves point within the current line. + (dired-move-to-filename) + (let ((mode-len 10) ; Length of mode string. + ;; As in `dired.el', but with subexpressions \1=inode, \2=blks: + ;; GNU `ls -hs' suffixes the block count with a unit and prints it as a float; FreeBSD does neither. + ;; $$$$$$ (dired-re-inode-size "\\s *\\([0-9]*\\)\\s *\\([0-9]*\\) ?") + (dired-re-inode-size (if (> emacs-major-version 24) + "\\=\\s *\\([0-9]+\\s +\\)?\ +\\(?:\\([0-9]+\\(?:\\.[0-9]*\\)?[BkKMGTPEZY]?\\)? ?\\)" + "\\s *\\([0-9]*\\)\\s *\\([0-9]*\\) ?")) + pos) + (beginning-of-line) + (forward-char 2) + (search-forward-regexp dired-re-inode-size nil t) + ;; XXX Might be a size not followed by a unit prefix. Could set `blks' to `inode' if it were otherwise + ;; nil, with similar reasoning as for setting `gid' to `uid', but it would be even more whimsical. + (setq inode (and (match-string 1) (string-to-number (match-string 1))) + blks (and (match-string 2) (if (fboundp 'dired-x--string-to-number) + (dired-x--string-to-number (match-string 2)) ; Emacs 25+ + (string-to-number (match-string 2)))) + mode (buffer-substring (point) (+ mode-len (point)))) + (forward-char mode-len) + (unless (eq (char-after) ?\ ) (forward-char 1)) ; Skip any extended attributes marker ("." or "+"). + (setq nlink (read (current-buffer))) + ;; Karsten Wenger fixed uid. + + ;; Another issue is that GNU `ls -n' right-justifies numerical UIDs and GIDs, while FreeBSD + ;; left-justifies them, so do not rely on a specific whitespace layout. Both of them right-justify all + ;; other numbers, though. + ;; XXX Return a number if the `uid' or `gid' seems to be numerical? + ;; $$$$$$ (setq uid (buffer-substring (+ (point) 1) (progn (forward-word 1) (point)))) + (setq uid (buffer-substring (progn (skip-chars-forward " \t") (point)) + (progn (skip-chars-forward "^ \t") (point)))) + (cond ((> emacs-major-version 24) + (dired-move-to-filename) + (save-excursion + (setq time + ;; The regexp below tries to match from the last digit of the size field through a + ;; space after the date. Also, dates may have different formats depending on file age, + ;; so the date column need not be aligned to the right. + (buffer-substring (save-excursion (skip-chars-backward " \t") (point)) + (progn (re-search-backward directory-listing-before-filename-regexp) + (skip-chars-forward "^ \t") + (1+ (point)))) + size + (dired-x--string-to-number + ;; We know that there's some kind of number before point because the regexp search + ;; above succeeded. Not worth doing an extra check for leading garbage. + (buffer-substring (point) (progn (skip-chars-backward "^ \t") (point)))) + ;; If no `gid' is displayed, `gid' will be set to `uid' but user will then not reference + ;; it anyway in PREDICATE. + gid + (buffer-substring (progn (skip-chars-backward " \t") (point)) + (progn (skip-chars-backward "^ \t") (point))))) + (setq name (buffer-substring (point) (or (dired-move-to-end-of-filename t) (point))) + sym (if (diredp-looking-at-p " -> ") + (buffer-substring (progn (forward-char 4) (point)) (line-end-position)) + ""))) + (t + (re-search-forward + (if (< emacs-major-version 20) + "\\(Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|Sep\\|Oct\\|Nov\\|Dec\\)" + dired-move-to-filename-regexp)) + (goto-char (match-beginning 1)) + (forward-char -1) + (setq size (string-to-number (buffer-substring (save-excursion (backward-word 1) + (setq pos (point))) + (point)))) + (goto-char pos) + (backward-word 1) + ;; if no `gid' is displayed, `gid' will be set to `uid' but user will then not reference + ;; it anyway in PREDICATE. + (setq gid (buffer-substring (save-excursion (forward-word 1) (point)) (point)) + time (buffer-substring (match-beginning 1) (1- (dired-move-to-filename))) + name (buffer-substring (point) (or (dired-move-to-end-of-filename t) (point))) + sym (if (diredp-looking-at-p " -> ") + (buffer-substring (progn (forward-char 4) (point)) (line-end-position)) + ""))))) + ;; Vanilla Emacs uses `lexical-binding' = t, and it passes bindings to `eval' as a second arg. + ;; We use `lexical-binding' = nil, and anyway there should be no need to pass the bindings. + (eval predicate))) + (format "'%s file" predicate)))) + +(defun diredp-this-file-marked-p (&optional mark-char) + "Return non-nil if the file on this line is marked. +Optional arg MARK-CHAR is the type of mark to check. + If nil, then if the file has any mark, including `D', it is marked." + (and (dired-get-filename t t) (save-excursion + (beginning-of-line) + (if mark-char + (diredp-looking-at-p + (concat "^" (regexp-quote (char-to-string mark-char)))) + (not (diredp-looking-at-p "^ ")))))) + +(defun diredp-this-file-unmarked-p (&optional mark-char) + "Return non-nil if the file on this line is unmarked. +Optional arg MARK-CHAR is the type of mark to check. + If nil, then if the file has no mark, including `D', it is unmarked. + If non-nil, then it is unmarked for MARK-CHAR if it has no mark or + it has any mark except MARK-CHAR." + (and (dired-get-filename t t) (save-excursion + (beginning-of-line) + (if mark-char + (not (diredp-looking-at-p + (concat "^" (regexp-quote (char-to-string mark-char))))) + (diredp-looking-at-p "^ "))))) + +;;;###autoload +(defun diredp-mark-region-files (&optional unmark-p) ; Not bound + "Mark all of the files in the current region (if it is active). +With non-nil prefix arg, unmark them instead." + (interactive "P") + (let ((beg (min (point) (mark))) + (end (max (point) (mark))) + (inhibit-field-text-motion t)) ; Just in case. + (setq beg (save-excursion (goto-char beg) (line-beginning-position)) + end (save-excursion (goto-char end) (line-end-position))) + (let ((dired-marker-char (if unmark-p ?\040 dired-marker-char))) + (diredp-mark-if (and (<= (point) end) (>= (point) beg) (diredp-this-file-unmarked-p)) "region file")))) + +;;;###autoload +(defun diredp-unmark-region-files (&optional mark-p) ; Not bound + "Unmark all of the files in the current region (if it is active). +With non-nil prefix arg, mark them instead." + (interactive "P") + (let ((beg (min (point) (mark))) + (end (max (point) (mark))) + (inhibit-field-text-motion t)) ; Just in case. + (setq beg (save-excursion (goto-char beg) (line-beginning-position)) + end (save-excursion (goto-char end) (line-end-position))) + (let ((dired-marker-char (if mark-p dired-marker-char ?\040))) + (diredp-mark-if (and (<= (point) end) (>= (point) beg) (diredp-this-file-marked-p)) "region file")))) + +;;;###autoload +(defun diredp-flag-region-files-for-deletion () ; Not bound + "Flag all of the files in the current region (if it is active) for deletion." + (interactive) + (let ((beg (min (point) (mark))) + (end (max (point) (mark))) + (inhibit-field-text-motion t)) ; Just in case. + (setq beg (save-excursion (goto-char beg) (line-beginning-position)) + end (save-excursion (goto-char end) (line-end-position))) + (let ((dired-marker-char dired-del-marker)) + (diredp-mark-if (and (<= (point) end) (>= (point) beg) (diredp-this-file-unmarked-p ?\D)) + "region file")))) + +;;;###autoload +(defun diredp-toggle-marks-in-region (start end) ; Not bound + "Toggle marks in the region." + (interactive "r") + (save-excursion + (save-restriction + (if (not (fboundp 'dired-toggle-marks)) + ;; Pre-Emacs 22. Use bol, eol. If details hidden, show first. + (let ((details-hidden-p (and (boundp 'dired-details-state) (eq 'hidden dired-details-state)))) + (widen) + (when details-hidden-p (dired-details-show)) + (goto-char start) + (setq start (line-beginning-position)) + (goto-char end) + (setq end (line-end-position)) + (narrow-to-region start end) + (dired-toggle-marks) + (when details-hidden-p (dired-details-hide))) + (narrow-to-region start end) + (dired-toggle-marks)))) + (when (and (get-buffer-window (current-buffer)) (fboundp 'fit-frame-if-one-window)) + (fit-frame-if-one-window))) + + +;;; Mouse 3 menu. +;;;;;;;;;;;;;;;;; + +(defvar diredp-file-line-overlay nil) + +;;;###autoload +(defun diredp-mouse-3-menu (event) ; Bound to `mouse-3' + "Dired pop-up `mouse-3' menu, for files in selection or current line." + (interactive "e") + (if (not (and (fboundp 'mouse3-dired-use-menu) (diredp-nonempty-region-p))) + ;; No `mouse3.el' or no region. + (if (diredp-nonempty-region-p) + ;; Region + (let ((reg-choice (x-popup-menu + event + (list "Files in Region" + (list "" + '("Mark" . diredp-mark-region-files) + '("Unmark" . diredp-unmark-region-files) + '("Toggle Marked/Unmarked" . + diredp-toggle-marks-in-region) + '("Flag for Deletion" . + diredp-flag-region-files-for-deletion)))))) + (when reg-choice (call-interactively reg-choice))) + ;; Single file/dir (no region). + (let ((mouse-pos (event-start event)) + ;; Do not use `save-excursion', because some commands will move point on purpose. + ;; Just save original point and return to it unless MOVEP is set to non-nil. + (opoint (point)) + (movep nil) + (inhibit-field-text-motion t) ; Just in case. + choice bol eol file/dir-name) + (with-current-buffer (window-buffer (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (setq bol (line-beginning-position) + eol (line-end-position)) + (unwind-protect + (when (setq file/dir-name (and (not (eobp)) (dired-get-filename nil t))) + (if diredp-file-line-overlay ; Don't re-create if exists. + (move-overlay diredp-file-line-overlay bol eol (current-buffer)) + (setq diredp-file-line-overlay (make-overlay bol eol)) + (overlay-put diredp-file-line-overlay 'face 'region)) + (sit-for 0) + (let ((map + (easy-menu-create-menu + "This File" + `( + ("Bookmark" :visible (featurep 'bookmark+) + ["Bookmark..." diredp-bookmark-this-file] + ["Add Tags..." diredp-tag-this-file + :visible (featurep 'bookmark+)] + ["Remove Tags..." diredp-untag-this-file + :visible (featurep 'bookmark+)] + ["Remove All Tags" diredp-remove-all-tags-this-file + :visible (featurep 'bookmark+)] + ["Copy Tags" diredp-copy-tags-this-file + :visible (featurep 'bookmark+)] + ["Paste Tags (Add)" diredp-paste-add-tags-this-file + :visible (featurep 'bookmark+)] + ["Paste Tags (Replace)" diredp-paste-replace-tags-this-file + :visible (featurep 'bookmark+)] + ["Set Tag Value..." diredp-set-tag-value-this-file + :visible (featurep 'bookmark+)] + ) + ["Describe" ',(if (if (> emacs-major-version 21) + (require 'help-fns+ nil t) + (require 'help+20 nil t)) + 'diredp-describe-file + 'diredp-describe-autofile)] ; Requires `bookmark+.el' + ;; Stuff from `Marks' menu. + ["Mark" dired-mark + :visible (not (eql (dired-file-marker file/dir-name) + dired-marker-char))] + ["Unmark" dired-unmark + :visible (dired-file-marker file/dir-name)] + ["Flag for Deletion" dired-flag-file-deletion + :visible (not (eql (dired-file-marker file/dir-name) + dired-del-marker))] + ["Delete..." diredp-delete-this-file] + "--" ; ------------------------------------------------------ + ;; Stuff from `Single' / `Multiple' menus. + ["Open" dired-find-file] + ["Open in Other Window" dired-find-file-other-window] + ["Open in Other Frame" diredp-find-file-other-frame] + ["Open Associated Windows App" dired-w32-browser + :visible (featurep 'w32-browser)] + ["Open in Windows Explorer" dired-w32explore + :visible (featurep 'w32-browser)] + ["View (Read Only)" dired-view-file] + ["--" 'ignore ; ------------------------------------------------- + :visible (or (atom (diredp-this-subdir)) ; Subdir line. + (not (equal (expand-file-name (dired-current-directory)) + (expand-file-name default-directory))))] ; Not top. + ["Insert This Subdir" + (lambda () (interactive) + (call-interactively #'dired-maybe-insert-subdir) + (setq movep t)) + :visible (and (atom (diredp-this-subdir)) + (not (assoc (file-name-as-directory (diredp-this-subdir)) + dired-subdir-alist))) + :enable (atom (diredp-this-subdir))] + ["Go To Inserted Subdir" + (lambda () (interactive) + (call-interactively #'dired-maybe-insert-subdir) + (setq movep t)) + :visible (and (atom (diredp-this-subdir)) + (assoc (file-name-as-directory (diredp-this-subdir)) + dired-subdir-alist)) + :enable (atom (diredp-this-subdir)) + :keys "i"] + ["Remove This Inserted Subdir" dired-kill-subdir + :visible (not (equal + (expand-file-name (dired-current-directory)) + (expand-file-name default-directory)))] ; In subdir, not top. + ["Remove This Inserted Subdir and Lower" diredp-kill-this-tree + :visible (and (fboundp 'diredp-kill-this-tree) + (not (equal + (expand-file-name (dired-current-directory)) + (expand-file-name default-directory))))] ; In subdir, not top. + ["Dired This Inserted Subdir (Tear Off)" + (lambda () (interactive) (diredp-dired-this-subdir t)) + :visible (not (equal (expand-file-name (dired-current-directory)) + (expand-file-name default-directory)))] ; In subdir, not top. + "--" ; ------------------------------------------------------ + ["Compare..." diredp-ediff] + ["Diff..." dired-diff] + ["Diff with Backup" dired-backup-diff] + + ["Bookmark..." diredp-bookmark-this-file + :visible (not (featurep 'bookmark+))] + "--" ; ------------------------------------------------------ + ["Rename to..." diredp-rename-this-file] + ["Capitalize" diredp-capitalize-this-file] + ["Upcase" diredp-upcase-this-file] + ["Downcase" diredp-downcase-this-file] + "--" ; ------------------------------------------------------ + ["Copy to..." diredp-copy-this-file] + ["Symlink to (Relative)..." diredp-relsymlink-this-file] + ["Symlink to..." diredp-symlink-this-file] + ["Hardlink to..." diredp-hardlink-this-file] + "--" ; ------------------------------------------------------ + ["Shell Command..." diredp-shell-command-this-file] + ["Asynchronous Shell Command..." + diredp-async-shell-command-this-file] + ["Print..." diredp-print-this-file] + ["Grep" diredp-grep-this-file] + ["Compress/Uncompress" diredp-compress-this-file] + ["Byte-Compile" diredp-byte-compile-this-file] + ["Load" diredp-load-this-file] + "--" ; ------------------------------------------------------ + ["Change Timestamp..." diredp-touch-this-file] + ["Change Mode..." diredp-chmod-this-file] + ["Change Group..." diredp-chgrp-this-file + :visible (fboundp 'diredp-chgrp-this-file)] + ["Change Owner..." diredp-chown-this-file + :visible (fboundp 'diredp-chown-this-file)])))) + (when diredp-file-line-overlay + (delete-overlay diredp-file-line-overlay)) + (setq choice (x-popup-menu event map)) + (when choice (call-interactively (lookup-key map (apply 'vector choice)))))) + (unless movep (goto-char opoint)))))) + ;; `mouse3.el' and active region. + (unless (eq mouse3-dired-function 'mouse3-dired-use-menu) + (funcall #'mouse3-dired-use-menu) + (revert-buffer)) + (let ((last-command 'mouse-save-then-kill)) (mouse-save-then-kill event)))) + + +;; REPLACE ORIGINAL in `dired.el' for Emacs 20. +;; +;; Allow `.' and `..', by using non-nil second arg to `dired-get-filename'. +;; +(when (< emacs-major-version 21) + (defun dired-find-file () ; Bound to `RET' + "In Dired, visit the file or directory named on this line." + (interactive) + (let* ((dgf-result (or (dired-get-filename nil t) (error "No file on this line"))) + (file-name (file-name-sans-versions dgf-result t))) + (if (file-exists-p file-name) + (find-file file-name) + (if (file-symlink-p file-name) + (error "File is a symlink to a nonexistent target") + (error "File no longer exists; type `g' to update Dired buffer")))))) + +;;;###autoload +(defun diredp-find-file-other-frame () ; Bound to `C-o' + "In Dired, visit this file or directory in another frame." + (interactive) + (find-file-other-frame (file-name-sans-versions (dired-get-filename nil t) t))) + +;;;###autoload +(defun diredp-mouse-find-file-other-frame (event) ; Bound to `M-mouse-2' + "In Dired, visit file or directory clicked on in another frame." + (interactive "e") + (let ((pop-up-frames t)) (dired-mouse-find-file-other-window event))) + + +;; REPLACE ORIGINAL in `dired.el'. +;; +;; Allow `.' and `..', by using non-nil second arg to `dired-get-filename'. +;; +;;;###autoload +(defun dired-mouse-find-file-other-window (event) ; Bound to `mouse-2' + "In Dired, visit the file or directory name you click on." + (interactive "e") + (let (file) + (with-current-buffer (window-buffer (posn-window (event-end event))) + (save-excursion (goto-char (posn-point (event-end event))) + (setq file (dired-get-filename nil t)))) + (unless (stringp file) (error "No file here")) + (select-window (posn-window (event-end event))) + (find-file-other-window (file-name-sans-versions file t)))) + +;;;###autoload +(defun diredp-mouse-view-file (event) ; Not bound + "Examine this file in view mode, returning to Dired when done. +When file is a directory, show it in this buffer if it is inserted; +otherwise, display it in another buffer." + (interactive "e") + (let (file) + (with-current-buffer (window-buffer (posn-window (event-end event))) + (save-excursion (goto-char (posn-point (event-end event))) + (setq file (dired-get-filename nil t)))) + (select-window (posn-window (event-end event))) + (if (file-directory-p file) + (or (and (cdr dired-subdir-alist) (dired-goto-subdir file)) (dired file)) + (view-file file)))) ; In `view.el'. + +;;;###autoload +(defun diredp-mouse-ediff (event) ; Not bound + "Compare this file (pointed by mouse) with file FILE2 using `ediff'. +FILE2 defaults to this file as well. If you enter just a directory +name for FILE2, then this file is compared with a file of the same +name in that directory. FILE2 is the second file given to `ediff'; +this file is the first given to it." + (interactive "e") + (require 'ediff) + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (call-interactively 'diredp-ediff))) + +;;;###autoload +(defun diredp-mouse-diff (event &optional switches) ; Not bound + "Compare this file (pointed by mouse) with file FILE2 using `diff'. +FILE2 defaults to the file at the mark. This file is the first file +given to `diff'. With prefix arg, prompt for second arg SWITCHES, +which are options for `diff'." + (interactive "e") + (let ((default (and (mark t) (save-excursion (goto-char (mark t)) + (dired-get-filename t t)))) + (mouse-pos (event-start event))) + (require 'diff) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (let ((file2 (read-file-name (format "Diff %s with: %s" + (dired-get-filename t) + (if default (concat "(default " default ") ") "")) + (dired-current-directory) default t))) + (setq switches (and current-prefix-arg + (if (fboundp 'icicle-read-string-completing) ; In `icicles-fn.el' + (icicle-read-string-completing "Options for diff: " + (if (stringp diff-switches) + diff-switches + (mapconcat #'identity diff-switches " ")) + (lambda (c) + (diredp-string-match-p "switches" + (symbol-name c)))) + (read-string "Options for diff: " (if (stringp diff-switches) + diff-switches + (mapconcat #'identity diff-switches " ")))))) + (diff file2 (dired-get-filename t) switches)))) + +;;;###autoload +(defun diredp-mouse-backup-diff (event) ; Not bound + "Diff this file with its backup file or vice versa. +Use the latest backup, if there are several numerical backups. +If this file is a backup, diff it with its original. +The backup file is the first file given to `diff'. +With prefix arg, prompt for SWITCHES which are the options for `diff'." + (interactive "e") + (let ((switches (and current-prefix-arg + (if (fboundp 'icicle-read-string-completing) ; In `icicles-fn.el' + (icicle-read-string-completing "Options for diff: " + (if (stringp diff-switches) + diff-switches + (mapconcat #'identity diff-switches " ")) + (lambda (c) + (diredp-string-match-p "switches" + (symbol-name c)))) + (read-string "Options for diff: " (if (stringp diff-switches) + diff-switches + (mapconcat #'identity diff-switches " ")))))) + (mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (diff-backup (dired-get-filename) switches))) + +;;;###autoload +(defun diredp-mouse-mark (event) ; Not bound + "In Dired, mark this file. +If on a subdir headerline, mark all its files except `.' and `..'. + +Use \\[dired-unmark-all-files] to remove all marks, +and \\[dired-unmark] on a subdir to remove the marks in this subdir." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (if (and (cdr dired-subdir-alist) (dired-get-subdir)) + (save-excursion (dired-mark-subdir-files)) + (let ((buffer-read-only nil)) + (dired-repeat-over-lines 1 #'(lambda () (delete-char 1) (insert dired-marker-char))) + (diredp-previous-line 1)))) + +;;;###autoload +(defun diredp-mouse-unmark (event) ; Not bound + "In Dired, unmark this file. +If looking at a subdir, unmark all its files except `.' and `..'." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (let ((dired-marker-char ?\040)) (dired-mark nil)) + (diredp-previous-line 1)) + +;;; This can be bound to [C-down-mouse-1] to give behavior similar to Windows Explorer. +;;; However, Emacs generally uses [C-down-mouse-1] for `mouse-buffer-menu'. +;;;###autoload +(defun diredp-mouse-mark/unmark (event) ; Not bound + "Mark/unmark file or directory at mouse EVENT." + (interactive "e") + (let* ((mouse-pos (event-start event)) + (inhibit-field-text-motion t) ; Just in case. + (file/dir-name (with-current-buffer (window-buffer (posn-window mouse-pos)) + (save-excursion + (goto-char (posn-point mouse-pos)) + (and (not (eobp)) (dired-get-filename nil t)))))) + ;; Return nil iff not on a file or directory name. + (and file/dir-name (cond ((dired-file-marker file/dir-name) + (diredp-mouse-unmark event) + (message "Unmarked: %s" file/dir-name)) + (t + (diredp-mouse-mark event) + (message "Marked: %s" file/dir-name)))))) + +;; This can be bound to [S-mouse-1] to give behavior similar to Windows Explorer. +;; If you do that, consider binding `diredp-mouse-mark/unmark' to `C-mouse-1'. +;; Alternatively, just bind `diredp-mouse-mark/unmark-mark-region-files' to [S-mouse-1]. +;;;###autoload +(defun diredp-mouse-mark-region-files (event) ; Bound to `S-mouse-1' + "Mark files between point and the mouse." + (interactive "e") + (call-interactively 'mouse-save-then-kill) + (diredp-mark-region-files)) + +;; This can be bound to [S-mouse-1] to give behavior similar to Windows Explorer. +;; If you don't bind `diredp-mouse-mark/unmark' to, for instance, `C-mouse-1', then +;; Consider binding this to [S-mouse-1]. +;;;###autoload +(defun diredp-mouse-mark/unmark-mark-region-files (event) ; Not bound + "Mark/unmark file or mark files in region. +If the file the cursor is on is marked, then mark all files between it + and the line clicked (included). +Otherwise (cursor's file is unmarked): + If the file clicked is marked, then unmark it. + If it is unmarked, then mark it." + (interactive "e") + (let ((mouse-pos (event-start event))) + ;; If same click same line as cursor, or cursor's line is marked, + ;; Then toggle the clicked line's mark. + ;; Else mark all files in region between point and clicked line (included). + (if (or (eq (count-lines (point-min) (posn-point mouse-pos)) + (count-lines (point-min) (point))) + (equal dired-marker-char (dired-file-marker (dired-get-filename nil t)))) + (diredp-mouse-mark/unmark event) + (call-interactively 'mouse-save-then-kill) + (diredp-mark-region-files)))) + +;;;###autoload +(defun diredp-mouse-flag-file-deletion (event) ; Not bound + "In Dired, flag this file for deletion. +If on a subdir headerline, mark all its files except `.' and `..'." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (let ((dired-marker-char dired-del-marker)) (dired-mark 1)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-mouse-do-copy (event) ; Not bound + "In Dired, copy this file. +This normally preserves the last-modified date when copying." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-create-files 'copy #'dired-copy-file (if dired-copy-preserve-time "Copy [-p]" "Copy") + 1 dired-keep-marker-copy)) + +;;;###autoload +(defun diredp-mouse-do-rename (event) ; Not bound + "In Dired, rename this file." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-create-files 'move #'dired-rename-file "Move" 1 dired-keep-marker-rename "Rename")) + +;;;###autoload +(defun diredp-mouse-upcase (event) ; Not bound + "In Dired, rename this file to upper case." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-rename-non-directory #'upcase "Rename to uppercase:" nil)) + +;;;###autoload +(defun diredp-mouse-downcase (event) ; Not bound + "In Dired, rename this file to lower case." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-rename-non-directory #'downcase "Rename to lowercase:" nil)) + +;;;###autoload +(defun diredp-mouse-do-delete (event) ; Not bound + "In Dired, delete this file, upon confirmation." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (diredp-internal-do-deletions (dired-map-over-marks (cons (dired-get-filename) (point)) 1) + 1 + 'USE-TRASH-CAN) ; This arg is for Emacs 24+ only. + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-mouse-do-shell-command (event) ; Not bound + "Run a shell COMMAND on this file. +If there is output, it goes to a separate buffer. + +No automatic redisplay of Dired buffers is attempted, as there's no +telling what files the command may have changed. Type +\\[dired-do-redisplay] to redisplay. + +The shell command has the top level directory as working directory, so +output files usually are created there instead of in a subdir." + ;;Functions dired-run-shell-command and dired-shell-stuff-it do the + ;;actual work and can be redefined for customization. + (interactive "e") + (lexical-let ((mouse-pos (event-start event)) + (command (dired-read-shell-command "! on %s: " nil (dired-get-marked-files t nil)))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-bunch-files (- 10000 (length command)) + (lambda (&rest files) (dired-run-shell-command (dired-shell-stuff-it command files t 1))) + nil + (dired-get-marked-files t 1)))) + +;;;###autoload +(defun diredp-mouse-do-symlink (event) ; Not bound + "Make symbolic link to this file." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-create-files 'symlink #'make-symbolic-link "Symlink" 1 dired-keep-marker-symlink)) + +;;;###autoload +(defun diredp-mouse-do-hardlink (event) ; Not bound + "Make hard link (alias) to this file." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-create-files 'hardlink #'add-name-to-file "Hardlink" 1 dired-keep-marker-hardlink)) + +;;;###autoload +(defun diredp-mouse-do-print (event) ; Not bound + "Print this file. +Uses the shell command coming from variables `lpr-command' and +`lpr-switches' as default." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (let* ((file (dired-get-filename)) + (command (dired-mark-read-string "Print %s with: " + (apply 'concat lpr-command " " lpr-switches) + 'print 1 (list file)))) + (dired-run-shell-command (dired-shell-stuff-it command (list file) nil)))) + +;;;###autoload +(defun diredp-mouse-do-grep (event) ; Not bound + "Run grep against this file." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (unless grep-command (grep-compute-defaults)) + (grep (diredp-do-grep-1 (list (dired-get-filename t))))) + +;;;###autoload +(defun diredp-mouse-do-compress (event) ; Not bound + "Compress or uncompress this file." + (interactive "e") + (let ((mouse-pos (event-start event)) + (dired-no-confirm t)) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'dired-compress 1 'compress t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-mouse-do-byte-compile (event) ; Not bound + "Byte compile this file." + (interactive "e") + (let ((mouse-pos (event-start event)) + (dired-no-confirm t)) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'dired-byte-compile 1 'byte-compile t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-mouse-do-load (event) ; Not bound + "Load this Emacs Lisp file." + (interactive "e") + (let ((mouse-pos (event-start event)) + (dired-no-confirm t)) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos)) + (dired-map-over-marks-check #'dired-load 1 'load t)) + (diredp-previous-line 1)) + +;;;###autoload +(defun diredp-mouse-do-chmod (event) ; Not bound + "Change the mode of this file. +This calls chmod, so symbolic modes like `g+w' are allowed." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-chxxx "Mode" "chmod" 'chmod 1) + (diredp-previous-line 1)) + +(unless (memq system-type '(windows-nt ms-dos)) + (defun diredp-mouse-do-chgrp (event) ; Not bound + "Change the group of this file." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-chxxx "Group" "chgrp" 'chgrp 1) + (diredp-previous-line 1))) + +(unless (memq system-type '(windows-nt ms-dos)) + (defun diredp-mouse-do-chown (event) ; Not bound + "Change the owner of this file." + (interactive "e") + (let ((mouse-pos (event-start event))) + (select-window (posn-window mouse-pos)) + (goto-char (posn-point mouse-pos))) + (dired-do-chxxx "Owner" dired-chown-program 'chown 1) + (diredp-previous-line 1))) + + +;;; Breadcrumbs + +(when (fboundp 'define-minor-mode) + + ;; Macro `define-minor-mode' is not defined in Emacs 20, so in order to be able to byte-compile + ;; this file in Emacs 20, prohibit byte-compiling of the `define-minor-mode' call. + ;; + (eval '(define-minor-mode diredp-breadcrumbs-in-header-line-mode + "Toggle the use of breadcrumbs in Dired header line. +With arg, show breadcrumbs iff arg is positive." + :init-value nil :group 'header-line :group 'Dired-Plus + (unless (derived-mode-p 'dired-mode) + (error "You must be in Dired or a mode derived from it to use this command")) + (if diredp-breadcrumbs-in-header-line-mode + (diredp-set-header-line-breadcrumbs) + (setq header-line-format (default-value 'header-line-format))))) + + (defun diredp-set-header-line-breadcrumbs () + "Show a header line with breadcrumbs to parent directories." + (let ((parent (diredp-parent-dir default-directory)) + (dirs ()) + (text "")) + (while parent + (push parent dirs) + (setq parent (diredp-parent-dir parent))) + (dolist (dir dirs) + (let* ((crumbs-map (make-sparse-keymap)) + (menu-map (make-sparse-keymap "Breadcrumbs in Header Line")) + ;; The next three are for showing the root as absolute and the rest as relative. + (rootp (diredp-root-directory-p dir)) + (parent-rootp (and (not rootp) (diredp-root-directory-p (diredp-parent-dir dir)))) + (rdir dir)) + ;; (define-key crumbs-map [header-line mouse-3] menu-map) + (unless rootp (setq rdir (file-name-nondirectory (directory-file-name dir)))) + (when dir + (setq rdir (propertize rdir + 'local-map (progn (define-key crumbs-map [header-line mouse-1] + `(lambda () (interactive) + (dired ,dir dired-actual-switches))) + (define-key crumbs-map [header-line mouse-2] + `(lambda () (interactive) + (dired-other-window ,dir dired-actual-switches))) + crumbs-map) + 'mouse-face 'mode-line-highlight + ;;'help-echo "mouse-1: Dired; mouse-2: Dired in other window; mouse-3: Menu")) + 'help-echo "mouse-1: Dired; mouse-2: Dired in other window")) + (setq text (concat text (if (or rootp parent-rootp) " " " / ") rdir))))) + (make-local-variable 'header-line-format) + (setq header-line-format text))) + + ;; Users can do this. + ;; + ;; (add-hook 'dired-before-readin-hook 'diredp-breadcrumbs-in-header-line-mode) + + ) + + +;;; `Dired+' Help + +;;;###autoload +(defun diredp-describe-mode (&optional buffer) + "Describe Dired mode, including Dired+ features. +This is `describe-mode' plus a description of Dired+ features. +For just the latter, use \\`\\[diredp-dired-plus-help]'." + (interactive "@") + (unless (derived-mode-p 'dired-mode) + (error "Use `diredp-dired-plus-help' if you want information about Dired+")) + (with-current-buffer (or buffer (current-buffer)) (describe-mode)) + (with-current-buffer (get-buffer-create "*Help*") + (save-excursion + (goto-char (point-min)) + (diredp-dired-plus-help-link) + (let ((buffer-read-only nil)) (insert "\n")) + (when (re-search-forward "Keybindings:\nkey\\s-+binding\n---\\s-+-------" nil t) + (goto-char (match-beginning 0)) + (let ((buffer-read-only nil)) + (insert "\f\n") + (diredp-dired-plus-description+links) + (insert "\f\n")))))) + +;;;###autoload +(defun diredp-dired-plus-help () + "Describe Dired+." + (interactive "@") + (diredp-with-help-window "*Help*" (diredp-dired-plus-description+links))) + +(defun diredp-dired-plus-description+links () + "Insert Dired+ help text in `*Help*'." + (with-current-buffer (get-buffer-create "*Help*") + (let ((buffer-read-only nil)) + (save-restriction + (narrow-to-region (point) (point)) + (diredp-dired-plus-help-link) + (insert (diredp-dired-plus-description)) + (goto-char (point-max)) + (insert "\n") + (diredp-dired-plus-help-link))))) + +(when (and (> emacs-major-version 21) + (require 'help-mode nil t) + (get 'help-xref 'button-category-symbol)) ; `button.el' + (define-button-type 'diredp-help-button + :supertype 'help-xref + 'help-function #'(lambda () (browse-url "https://www.emacswiki.org/emacs/DiredPlus")) + 'help-echo + (purecopy "mouse-2, RET: Dired+ documentation on the Emacs Wiki (requires \ +Internet access)"))) + +(defun diredp-dired-plus-help-link () + "Add Web link for Dired+ help, and reminder about sending bug report." + ;; Don't bother to do this for Emacs 21.3. Its `help-insert-xref-button' is different. + (when (and (> emacs-major-version 21) + (require 'help-mode nil t) + (fboundp 'help-insert-xref-button)) ; `help-mode.el'. + (let ((buffer-read-only nil)) + (help-insert-xref-button "[Dired+ Help on the Web]" 'diredp-help-button) + (insert (substitute-command-keys + "\t\tSend a Dired+ bug report:\n\t\t\t\t\t`\\[diredp-send-bug-report]'\n"))))) + +(defun diredp-dired-plus-description () + "Dired+ description." + (substitute-command-keys + (concat + "\\\ + Dired+ Features + --------------- + +To see or customize the Dired+ options or faces, use +`M-x customize-option diredp TAB' or `M-x customize-face diredp TAB'. + +Most keys listed here are in addition to those for vanilla Dired. + +Menus +----- + +Many Dired+ actions are available from the menu-bar menus and the +`mouse-3' context menu. This may include commands shown here as not +being bound to keys (i.e., listed as `M-x ...'). + +General Here +------------ + +" + (and (fboundp 'diredp-w32-drives) + " \\[diredp-w32-drives]\t\t- Go up to a list of MS Windows drives +") + (and (fboundp 'dired-hide-details-mode) + " \\[dired-hide-details-mode]\t\t- Hide/show details +") + + " \\[revert-buffer]\t\t- Refresh (sync and show all) + \\[diredp-toggle-find-file-reuse-dir]\t- Toggle reusing directories +" + " \\[diredp-marked-other-window]\t\t- Open Dired on marked files here + \\[diredp-dired-inserted-subdirs]\t\t- Dired separately each subdir inserted here +" + (and (featurep 'bookmark+) + " \\[diredp-highlight-autofiles-mode]\t- Toggle autofile highlighting + +") + + "General Globally +---------------- + +\\\ + \\[diredp-add-to-dired-buffer]\t- Add files to a Dired buffer + \\[diredp-fileset]\t- Open Dired on files in a fileset + \\[diredp-dired-recent-dirs]\t- Open Dired on recently used dirs + \\[diredp-dired-union]\t- Create union of some Dired buffers + \\[diredp-dired-for-files]\t- Open Dired on files located anywhere +\\\ + +Mouse +----- + + \\[diredp-mouse-3-menu]\t- Context-sensitive menu +" + + (and (where-is-internal 'diredp-mouse-describe-file dired-mode-map) + " \\[diredp-mouse-describe-file]\t- Describe file +") + + (and (where-is-internal 'diredp-mouse-describe-autofile dired-mode-map) + " \\[diredp-mouse-describe-autofile]\t- Describe autofile +") + + " \\[diredp-mouse-mark-region-files]\t\t- Mark all in region +" + + (and (fboundp 'dired-mouse-w32-browser) ; In `w32-browser.el'. + (where-is-internal 'dired-mouse-w32-browser dired-mode-map) + " \\[dired-mouse-w32-browser]\t\t- MS Windows `Open' action +") + (and (fboundp 'dired-mouse-w32-browser-reuse-dir-buffer) ; In `w32-browser.el'. + (where-is-internal 'dired-mouse-w32-browser-reuse-dir-buffer dired-mode-map) + " \\[dired-mouse-w32-browser-reuse-dir-buffer]\t- MS Windows `Open' action +") + + (and (where-is-internal 'dired-mouse-find-file dired-mode-map) + " \\[dired-mouse-find-file]\t- Open in this window +") + (and (where-is-internal 'diredp-mouse-find-file-reuse-dir-buffer dired-mode-map) + " \\[diredp-mouse-find-file-reuse-dir-buffer]\t- Open in this window +") + + (and (where-is-internal 'dired-mouse-find-file-other-window dired-mode-map) + " \\[dired-mouse-find-file-other-window]\t\t- Open in another window +") + + " \\[diredp-mouse-find-file-other-frame]\t\t- Open in another frame +" + + " +Marking +------- + + \\[dired-mark]\t\t- Mark this file/dir + \\[dired-unmark]\t\t- Unmark this file/dir + \\[dired-toggle-marks]\t\t- Toggle marked/unmarked + \\[dired-mark-sexp]\t\t- Mark all satisfying a predicate + \\[dired-unmark-all-marks]\t\t- Unmark all + \\[diredp-mark/unmark-extension]\t\t- Mark/unmark all that have a given extension +" + + (and (fboundp 'dired-mark-omitted) ; In `dired-x.el' Emacs 22+. + " \\[dired-mark-omitted]\t\t- Mark omitted +") + + " \\[diredp-mark-files-tagged-regexp]\t\t- Mark those with a tag that matches a regexp + \\[diredp-unmark-files-tagged-regexp]\t\t- Unmark those with a tag that matches a regexp + \\[diredp-mark-files-tagged-all]\t\t- Mark those with all of the given tags + \\[diredp-unmark-files-tagged-all]\t\t- Unmark those with all of the given tags + \\[diredp-mark-files-tagged-some]\t\t- Mark those with some of the given tags + \\[diredp-unmark-files-tagged-some]\t\t- Unmark those with some of the given tags + \\[diredp-mark-files-tagged-not-all]\t- Mark those without some of the given tags + \\[diredp-unmark-files-tagged-not-all]\t- Unmark those without some of the given tags + \\[diredp-mark-files-tagged-none]\t- Mark those with none of the given tags + \\[diredp-unmark-files-tagged-none]\t- Unmark those with none of the given tags +" + + " +Current file/subdir (current line) +---------------------------------- + + \\[diredp-describe-file]\t- Describe + \\[dired-find-file]\t\t- Open +" + (and (fboundp 'dired-mouse-w32-browser) ; In `w32-browser.el'. + (where-is-internal 'dired-mouse-w32-browser dired-mode-map) + " \\[dired-mouse-w32-browser]\t- MS Windows `Open' action + \\[dired-w32explore]\t- MS Windows Explorer +") + + " \\[diredp-byte-compile-this-file]\t\t- Byte-compile + \\[diredp-compress-this-file]\t\t- Compress/uncompress + \\[diredp-print-this-file]\t\t- Print + \\[diredp-relsymlink-this-file]\t\t- Create relative symlink + \\[diredp-delete-this-file]\t\t- Delete (with confirmation) + \\[diredp-rename-this-file]\t\t- Rename + \\[diredp-capitalize-this-file]\t\t- Capitalize (rename) + \\[diredp-upcase-this-file]\t\t- Rename to uppercase + \\[diredp-downcase-this-file]\t\t- Rename to lowercase + \\[diredp-ediff]\t\t- Ediff + \\[diredp-bookmark-this-file]\t\t- Bookmark +" + (and (featurep 'bookmark+) + " \\[diredp-tag-this-file]\t\t- Add some tags to this file/dir + \\[diredp-untag-this-file]\t\t- Remove some tags from this file/dir + \\[diredp-remove-all-tags-this-file]\t\t- Remove all tags from this file/dir + \\[diredp-copy-tags-this-file]\t\t- Copy the tags from this file/dir + \\[diredp-paste-add-tags-this-file]\t\t- Paste (add) copied tags to this file/dir + \\[diredp-paste-replace-tags-this-file]\t\t- Paste (replace) tags for this file/dir + \\[diredp-set-tag-value-this-file]\t\t- Set a tag value for this file/dir +") + + (and (fboundp 'dired-mouse-w32-browser-reuse-dir-buffer) ; In `w32-browser.el'. + (where-is-internal 'dired-mouse-w32-browser-reuse-dir-buffer dired-mode-map) + " \\[dired-mouse-w32-browser-reuse-dir-buffer]\t- MS Windows `Open' action + \\[dired-w32explore]\t- MS Windows Explorer +") + + " +Marked (or next prefix arg) files & subdirs here +------------------------------------------------ +" + (and (fboundp 'dired-multiple-w32-browser) ; In `w32-browser.el'. + " + \\[dired-multiple-w32-browser]\t- MS Windows `Open' action +") + + + " \\[diredp-list-marked]\t\t- List marked files and directories + \\[diredp-insert-subdirs]\t\t- Insert marked subdirectories + \\[dired-copy-filename-as-kill]\t\t- Copy names for pasting + M-o \\[dired-copy-filename-as-kill]\t\t- Copy absolute names for pasting + \\[diredp-yank-files]\t\t- Paste files whose absolute names you copied + \\[dired-do-find-marked-files]\t\t- Visit + \\[dired-do-copy]\t\t- Copy + \\[dired-do-rename]\t\t- Rename/move + \\[diredp-do-grep]\t\t- Run `grep' + \\[dired-do-search]\t\t- Search +" + (and (fboundp 'dired-do-find-regexp) ; Emacs 25+ + " \\[dired-do-find-regexp]\t\t- Search using `find' +") + + (if (fboundp 'dired-do-query-replace-regexp) ; Emacs 22+ + " \\[dired-do-query-replace-regexp]\t\t- Query-replace +" + " \\[dired-do-query-replace]\t\t- Query-replace +") + + (and (fboundp 'dired-do-find-regexp-and-replace) + " \\[dired-do-find-regexp-and-replace]\t\t- Query-replace using `find' +") + + (and (fboundp 'dired-do-isearch) + " \\[dired-do-isearch]\t- Isearch + \\[dired-do-isearch-regexp]\t- Regexp isearch +") + + (and (fboundp 'dired-do-async-shell-command) + " \\[dired-do-async-shell-command]\t\t- Run shell command asynchronously +") + + " \\[dired-do-shell-command]\t\t- Run shell command + \\[diredp-marked-other-window]\t\t- Dired + \\[dired-do-compress]\t\t- Compress + \\[dired-do-byte-compile]\t\t- Byte-compile + \\[dired-do-load]\t\t- Load (Emacs Lisp) + \\[diredp-do-apply-function]\t\t- Apply Lisp function + \\[diredp-do-emacs-command]\t\t- Invoke Emacs command +" + (and (fboundp 'diredp-read-expression) ; Emacs 22+ + " \\[diredp-do-lisp-sexp]\t\t- Evaluate Lisp sexp +") + + " \\[diredp-omit-marked]\t- Omit + \\[diredp-omit-unmarked]\t- Omit unmarked +" + + (and (featurep 'bookmark+) + " + \\[diredp-do-tag]\t\t- Add some tags to marked + \\[diredp-do-untag]\t\t- Remove some tags from marked + \\[diredp-do-remove-all-tags]\t\t- Remove all tags from marked + \\[diredp-do-paste-add-tags]\t- Paste (add) copied tags to marked + \\[diredp-do-paste-replace-tags]\t\t- Paste (replace) tags for marked + \\[diredp-do-set-tag-value]\t\t- Set a tag value for marked + \\[diredp-mark-files-tagged-regexp]\t\t- Mark those with a tag that matches a regexp + \\[diredp-mark-files-tagged-all]\t\t- Mark those with all of the given tags + \\[diredp-mark-files-tagged-some]\t\t- Mark those with some of the given tags + \\[diredp-mark-files-tagged-not-all]\t- Mark those without some of the given tags + \\[diredp-mark-files-tagged-none]\t- Mark those with none of the given tags + \\[diredp-unmark-files-tagged-regexp]\t\t- Unmark those with a tag that matches a regexp + \\[diredp-unmark-files-tagged-all]\t\t- Unmark those with all of the given tags + \\[diredp-unmark-files-tagged-some]\t\t- Unmark those with some of the given tags + \\[diredp-unmark-files-tagged-not-all]\t- Unmark those without some of the given tags + \\[diredp-unmark-files-tagged-none]\t- Unmark those with none of the given tags") + + " + + \\[diredp-do-bookmark]\t\t- Bookmark +" + + (and (featurep 'bookmark+) + " \\[diredp-set-bookmark-file-bookmark-for-marked]\t\t- \ +Bookmark and create bookmark-file bookmark + \\[diredp-do-bookmark-in-bookmark-file]\t- Bookmark in specific bookmark file +") + + " +Here and below (in marked subdirs) +---------------------------------- +" + (and (fboundp 'dired-multiple-w32-browser) ; In `w32-browser.el'. + " + \\[diredp-multiple-w32-browser-recursive]\t- MS Windows `Open' action +") + + " \\[diredp-list-marked-recursive]\t\t- List marked files and directories + \\[diredp-insert-subdirs-recursive]\t\t- Insert marked subdirectories + \\[diredp-copy-filename-as-kill-recursive]\t\t- Copy names for pasting + \\[diredp-do-find-marked-files-recursive]\t\t\t- Visit + \\[diredp-do-print-recursive]\t\t\t- Print + \\[diredp-do-copy-recursive]\t\t\t- Copy + \\[diredp-do-move-recursive]\t\t\t- Move + \\[diredp-do-touch-recursive]\t\t- Touch (update timestamp) + \\[diredp-do-chmod-recursive]\t\t\t- Change mode + + \\[diredp-do-symlink-recursive]\t\t\t- Add symbolic links + \\[diredp-do-relsymlink-recursive]\t\t\t- Add relative symbolic links + \\[diredp-do-hardlink-recursive]\t\t\t- Add hard links + + \\[diredp-capitalize-recursive]\t\t- Capitalize + \\[diredp-downcase-recursive]\t\t- Downcase + \\[diredp-upcase-recursive]\t\t- Upcase +" + (and (fboundp 'epa-dired-do-encrypt) ; Emacs 23+ + " + \\[diredp-do-encrypt-recursive]\t\t- Encrypt + \\[diredp-do-decrypt-recursive]\t\t- Decrypt + \\[diredp-do-sign-recursive]\t\t- Sign + \\[diredp-do-verify-recursive]\t\t- Verify +") + + " + \\[diredp-do-grep-recursive]\t\t- `grep' + \\[diredp-do-search-recursive]\t\t\t- Search + \\[diredp-do-query-replace-regexp-recursive]\t\t\t- Query-replace + \\[diredp-do-isearch-recursive]\t\t- Isearch + \\[diredp-do-isearch-regexp-recursive]\t- Regexp isearch +" + (and (fboundp 'diredp-do-async-shell-command-recursive) ; Emacs 23+ + " + \\[diredp-do-async-shell-command-recursive]\t\t\t- Run shell command asynchronously +") + + " \\[diredp-do-shell-command-recursive]\t\t\t- Run shell command + \\[diredp-do-apply-function-recursive]\t\t\t- Apply Lisp function + + \\[diredp-marked-recursive-other-window]\t\t- Dired + \\[diredp-list-marked-recursive]\t\t- List + + \\[diredp-image-dired-comment-files-recursive]\t\t- Add image comment + \\[diredp-image-dired-display-thumbs-recursive]\t\t- Show thumbnail images + \\[diredp-image-dired-tag-files-recursive]\t\t- Tag images + \\[diredp-image-dired-delete-tag-recursive]\t\t- Delete image tags + + \\[diredp-do-bookmark-recursive]\t\t- Bookmark +" + (and (featurep 'bookmark+) + " \\[diredp-do-bookmark-in-bookmark-file-recursive]\t\t- Bookmark in bookmark file + \\[diredp-set-bookmark-file-bookmark-for-marked-recursive]\t\t- Create bookmark-file bookmark +") + + " + \\[diredp-mark-directories-recursive]\t\t- Mark directories + \\[diredp-mark-executables-recursive]\t\t- Mark executables + \\[diredp-mark-symlinks-recursive]\t\t- Mark symbolic links + \\[diredp-mark-files-containing-regexp-recursive]\t\t- Mark content regexp matches + \\[diredp-mark-files-regexp-recursive]\t\t- Mark filename regexp matches +" + (and (featurep 'bookmark+) + " \\[diredp-mark-autofiles-recursive]\t\t- Mark autofiles +") + " \\[diredp-flag-auto-save-files-recursive]\t\t\t- Flag auto-save + \\[diredp-do-delete-recursive]\t\t\t- Delete marked (not flagged) + \\[diredp-change-marks-recursive]\t\t- Change marks + \\[diredp-unmark-all-files-recursive]\t\t- Remove a given mark + \\[diredp-unmark-all-marks-recursive]\t\t\t- Remove all marks +" + (and (featurep 'bookmark+) +" + +Tagging +------- + + \\[diredp-tag-this-file]\t\t- Add some tags to this file/dir + \\[diredp-untag-this-file]\t\t- Remove some tags from this file/dir + \\[diredp-remove-all-tags-this-file]\t\t- Remove all tags from this file/dir + \\[diredp-copy-tags-this-file]\t\t- Copy the tags from this file/dir + \\[diredp-paste-add-tags-this-file]\t\t- Paste (add) copied tags to this file/dir + \\[diredp-paste-replace-tags-this-file]\t\t- Paste (replace) tags for this file/dir + \\[diredp-set-tag-value-this-file]\t\t- Set a tag value for this file/dir + \\[diredp-do-tag]\t\t- Add some tags to marked + \\[diredp-do-untag]\t\t- Remove some tags from marked + \\[diredp-do-remove-all-tags]\t\t- Remove all tags from marked + \\[diredp-do-paste-add-tags]\t- Paste (add) copied tags to marked + \\[diredp-do-paste-replace-tags]\t\t- Paste (replace) tags for marked + \\[diredp-do-set-tag-value]\t\t- Set a tag value for marked + \\[diredp-mark-files-tagged-regexp]\t\t- Mark those with a tag that matches a regexp + \\[diredp-mark-files-tagged-all]\t\t- Mark those with all of the given tags + \\[diredp-mark-files-tagged-some]\t\t- Mark those with some of the given tags + \\[diredp-mark-files-tagged-not-all]\t- Mark those without some of the given tags + \\[diredp-mark-files-tagged-none]\t- Mark those with none of the given tags + \\[diredp-unmark-files-tagged-regexp]\t\t- Unmark those with a tag that matches a regexp + \\[diredp-unmark-files-tagged-all]\t\t- Unmark those with all of the given tags + \\[diredp-unmark-files-tagged-some]\t\t- Unmark those with some of the given tags + \\[diredp-unmark-files-tagged-not-all]\t- Unmark those without some of the given tags + \\[diredp-unmark-files-tagged-none]\t- Unmark those with none of the given tags +") + + " +Bookmarking +----------- + + \\[diredp-bookmark-this-file]\t\t- Bookmark this file/dir + \\[diredp-do-bookmark]\t\t- Bookmark marked" + + (and (featurep 'bookmark+) + " + \\[diredp-set-bookmark-file-bookmark-for-marked]\t\t- \ +Bookmark marked and create bookmark-file bookmark + \\[diredp-do-bookmark-in-bookmark-file]\t- Bookmark marked, in specific bookmark file +") + + " \\[diredp-do-bookmark-recursive]\t- Bookmark marked, here and below +" + (and (featurep 'bookmark+) + " \\[diredp-do-bookmark-in-bookmark-file-recursive]\t- \ +Bookmark marked, here and below, in specific file + \\[diredp-set-bookmark-file-bookmark-for-marked-recursive]\t- \ +Set bookmark-file bookmark for marked here and below +") + + ))) + +(when (> emacs-major-version 21) + (defun diredp-nb-marked-in-mode-name () + "Show number of marked, flagged, and current-list lines in mode-line. +\(Flagged means flagged for deletion.) +If the current line is marked/flagged and there are others +marked/flagged after it then show `N/M', where `N' is the number +marked/flagged through the current line and `M' is the total number +marked/flagged. + +If the current line is for a file then show `L/T', where `L' is the +line number in the current listing and `T' is the number of files in +that listing. If option `diredp-count-.-and-..-flag' is non-nil then +count also `.' and `..'. + +Also abbreviate `mode-name', using \"Dired/\" instead of \"Dired by\"." + (let ((mname (format-mode-line mode-name))) + ;; Property `dired+-mode-name' indicates whether `mode-name' has been changed. + (unless (get-text-property 0 'dired+-mode-name mname) + (save-match-data + (setq mode-name + `(,(propertize (if (string-match "^[dD]ired \\(by \\)?\\(.*\\)" mname) + (format "Dired/%s" (match-string 2 mname)) + mname) + 'dired+-mode-name t) + (:eval (let* ((dired-marker-char (if (eq ?D dired-marker-char) + ?* ; `dired-do-flagged-delete' binds it. + dired-marker-char)) + (marked-regexp (dired-marker-regexp)) + (nb-marked (count-matches marked-regexp + (point-min) (point-max)))) + (if (not (> nb-marked 0)) + "" + (propertize + (format " %s%d%c" + (save-excursion + (forward-line 0) + (if (diredp-looking-at-p (concat marked-regexp ".*")) + (format "%d/" (1+ (count-matches + marked-regexp + (point-min) (point)))) + "")) + nb-marked dired-marker-char) + 'face 'diredp-mode-line-marked 'dired+-mode-name t)))) + (:eval (let* ((flagged-regexp (let ((dired-marker-char dired-del-marker)) + (dired-marker-regexp))) + (nb-flagged (count-matches flagged-regexp + (point-min) (point-max)))) + (if (not (> nb-flagged 0)) + "" + (propertize + (format " %s%dD" + (save-excursion + (forward-line 0) + (if (diredp-looking-at-p (concat flagged-regexp ".*")) + (format "%d/" (1+ (count-matches + flagged-regexp + (point-min) (point)))) + "")) + nb-flagged) + 'face 'diredp-mode-line-flagged)))) + (:eval (save-excursion + (let ((this 0) + (total 0) + (o-pt (line-beginning-position)) + (e-pt (or (condition-case nil + (let ((diredp-wrap-around-flag nil)) + (save-excursion + (diredp-next-subdir 1) + (line-beginning-position))) + (error nil)) + (save-excursion (goto-char (point-max)) (line-beginning-position))))) + (when dired-subdir-alist (dired-goto-subdir (dired-current-directory))) + (while (and (<= (point) e-pt) + (< (point) (point-max))) ; Hack to work around Emacs display-engine bug. + (when (condition-case nil + (dired-get-filename nil diredp-count-.-and-..-flag) + (error nil)) + (when (<= (line-beginning-position) o-pt) (setq this (1+ this))) + (setq total (1+ total))) + (forward-line 1)) + (if (not (> this 0)) (format " %d" total) (format " %d/%d" this total))))))))))) + + (add-hook 'dired-after-readin-hook 'diredp-nb-marked-in-mode-name) + ;; This one is needed for `find-dired', because it does not call `dired-readin'. + (add-hook 'dired-mode-hook 'diredp-nb-marked-in-mode-name)) + +;;;###autoload +(defun diredp-send-bug-report () + "Send a bug report about a Dired+ problem." + (interactive) + (browse-url (format (concat "mailto:" "drew.adams" "@" "oracle" ".com?subject=\ +Dired+ bug: \ +&body=Describe bug below, using a precise recipe that starts with `emacs -Q' or `emacs -q'. \ +File `dired+.el' has a header `Update #' that you can use to identify it.\ +%%0A%%0AEmacs version: %s.") + (emacs-version)))) + +(defun diredp-visit-ignore-regexp () ; Taken from `image-file-name-regexp'. + "Return a regular expression matching file names to skip. +This is used by `dired-visit-(next|previous)'." + (let ((exts-regexp (and diredp-visit-ignore-extensions + (concat "\\." (regexp-opt (nconc (mapcar #'upcase diredp-visit-ignore-extensions) + diredp-visit-ignore-extensions) + t) + "\\'")))) + (if diredp-visit-ignore-regexps + (mapconcat #'identity (if exts-regexp + (cons exts-regexp diredp-visit-ignore-regexps) + diredp-visit-ignore-regexps) + "\\|") + exts-regexp))) + +;;;###autoload +(defun diredp-visit-next-file (&optional arg) ; Bound to `C-down' + "Move down a line and visit its file in another window. +With numeric prefix arg N, move down N-1 lines first. + +After moving N lines, skip any lines with file names that match either +`diredp-visit-ignore-extensions' or `diredp-visit-ignore-regexps'. + +Kill the last buffer visited by a `dired-visit-*' command." + (interactive "p") + (dired-next-line arg) + (while (diredp-string-match-p (diredp-visit-ignore-regexp) (dired-get-file-for-visit)) + (dired-next-line 1)) + (diredp-visit-this-file)) + +;;;###autoload +(defun diredp-visit-previous-file (&optional arg) ; Bound to `C-up' + "Move up a line and visit its file in another window. +With numeric prefix arg N, move up N-1 lines first. + +After moving N lines, skip any lines with file names that match either +`diredp-visit-ignore-extensions' or `diredp-visit-ignore-regexps'. + +Kill the last buffer visited by a `dired-visit-*' command." + (interactive "p") + (dired-previous-line arg) + (while (diredp-string-match-p (diredp-visit-ignore-regexp) (dired-get-file-for-visit)) + (dired-previous-line 1)) + (diredp-visit-this-file)) + +;;;###autoload +(defun diredp-visit-this-file () ; Bound to `e' (replaces `dired-find-file' binding) + "View the file on this line in another window in the same frame. +If it was not already shown there then kill the previous buffer +visited by a `dired-visit-*' command. + +If it was already shown there, and if it and Dired are the only +windows there, then delete its window (toggle : show/hide the file)." + (interactive) + (let ((file (dired-get-file-for-visit)) + (obuf (current-buffer)) + (shown nil) + fwin) + (unless (or (and (fboundp 'window-parent) (window-parent)) + (not (one-window-p 'NOMINI))) + (split-window)) + (save-selected-window + (other-window 1) + (setq fwin (selected-window)) + (unless (or (setq shown (or (equal (current-buffer) (get-file-buffer file)) + (memq (current-buffer) (dired-buffers-for-dir file)))) + (equal obuf (current-buffer))) + (kill-buffer (current-buffer)))) + (if shown + (when (= 2 (count-windows 'NOMINI)) (delete-window fwin)) + (set-window-buffer fwin (find-file-noselect file))))) + +;;; Key Bindings. + + +;; Menu Bar. +;; New order is (left -> right): +;; +;; Dir Regexp Mark Multiple Single + +;; Get rid of menu bar predefined in `dired.el'. +(define-key dired-mode-map [menu-bar] nil) +;; Get rid of Edit menu bar menu to save space. +(define-key dired-mode-map [menu-bar edit] 'undefined) + + +;; `Single' menu. +;; +;; REPLACE ORIGINAL `Immediate' menu in `dired.el'. +;; +(defvar diredp-menu-bar-single-menu (make-sparse-keymap "Single")) +(define-key dired-mode-map [menu-bar immediate] (cons "Single" diredp-menu-bar-single-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-immediate-menu 'diredp-menu-bar-single-menu)) +(make-obsolete-variable 'diredp-menu-bar-immediate-menu 'diredp-menu-bar-single-menu) ; 2017-04-09 + +(if (fboundp 'diredp-describe-file) + (define-key diredp-menu-bar-single-menu [diredp-describe-file] + '(menu-item "Describe" diredp-describe-file + :help "Describe the file or directory at cursor")) + (define-key diredp-menu-bar-single-menu [diredp-describe-autofile] + '(menu-item "Describe" diredp-describe-autofile + :help "Describe the autofile at cursor" + :enable (featurep 'bookmark+)))) +(define-key diredp-menu-bar-single-menu [separator-describe] '("--")) ; --------------------- + +(when (fboundp 'diredp-chown-this-file) + (define-key diredp-menu-bar-single-menu [chown] + '(menu-item "Change Owner..." diredp-chown-this-file + :help "Change the owner of file at cursor"))) +(when (fboundp 'diredp-chgrp-this-file) + (define-key diredp-menu-bar-single-menu [chgrp] + '(menu-item "Change Group..." diredp-chgrp-this-file + :help "Change the group of file at cursor"))) +(define-key diredp-menu-bar-single-menu [chmod] + '(menu-item "Change Mode..." diredp-chmod-this-file + :help "Change mode (attributes) of file at cursor")) +(when (fboundp 'dired-do-touch) ; Emacs 22+ + (define-key diredp-menu-bar-single-menu [touch] + '(menu-item "Change Timestamp (`touch')..." diredp-touch-this-file + :help "Change the timestamp of file at cursor, using `touch'"))) +(define-key diredp-menu-bar-single-menu [separator-change] '("--")) ; ----------------------- + +(define-key diredp-menu-bar-single-menu [print] + '(menu-item "Print..." diredp-print-this-file + :help "Print file at cursor, supplying print command")) +(define-key diredp-menu-bar-single-menu [grep] + '(menu-item "Grep..." diredp-grep-this-file :help "Grep file at cursor")) +(define-key diredp-menu-bar-single-menu [compress] + '(menu-item "Compress/Uncompress" diredp-compress-this-file + :help "Compress/uncompress file at cursor")) +(define-key diredp-menu-bar-single-menu [command] + '(menu-item "Shell Command..." diredp-shell-command-this-file + :help "Run a shell command on file at cursor")) +(define-key diredp-menu-bar-single-menu [diredp-async-shell-command-this-file] + '(menu-item "Asynchronous Shell Command..." diredp-async-shell-command-this-file + :help "Run a shell command asynchronously on file at cursor")) +(define-key diredp-menu-bar-single-menu [compile] + '(menu-item "Byte Compile" diredp-byte-compile-this-file + :help "Byte-compile this Emacs Lisp file")) +(define-key diredp-menu-bar-single-menu [load] + '(menu-item "Load" diredp-load-this-file + :help "Load this Emacs Lisp file")) + +(when (fboundp 'mkhtml-dired-files) ; In `mkhtml.el'. + (define-key diredp-menu-bar-single-menu [mkhtml-dired-files] + '(menu-item "Create HTML" mkhtml-dired-files + :help "Create an HTML file corresponding to file at cursor"))) +(define-key diredp-menu-bar-single-menu [separator-misc] '("--")) ; ------------------------- + +(define-key diredp-menu-bar-single-menu [delete] + '(menu-item "Delete" diredp-delete-this-file :help "Delete file at cursor")) +(define-key diredp-menu-bar-single-menu [separator-delete] '("--")) ; ----------------------- + +(define-key diredp-menu-bar-single-menu [backup-diff] + '(menu-item "Diff with Backup" dired-backup-diff + :help "Diff file at cursor with its latest backup")) +(define-key diredp-menu-bar-single-menu [diff] + '(menu-item "Diff..." dired-diff + :help "Compare file at cursor with another file using `diff'")) +(define-key diredp-menu-bar-single-menu [ediff] + '(menu-item "Compare..." diredp-ediff :help "Compare file at cursor with another file")) +(define-key diredp-menu-bar-single-menu [separator-diff] '("--")) ; ------------------------- + +(define-key diredp-menu-bar-single-menu [diredp-kill-this-tree] + '(menu-item "Remove This Inserted Subdir and Lower" diredp-kill-this-tree + :visible (and (fboundp 'diredp-kill-this-tree) + (not (equal + (expand-file-name (dired-current-directory)) + (expand-file-name default-directory)))))) ; In subdir, not top. +(define-key diredp-menu-bar-single-menu [dired-kill-subdir] + '(menu-item "Remove This Inserted Subdir" dired-kill-subdir + :visible (not (equal (expand-file-name (dired-current-directory)) + (expand-file-name default-directory))))) ; In subdir, not top. +(define-key diredp-menu-bar-single-menu [diredp-dired-this-subdir] + '(menu-item "Dired This Inserted Subdir (Tear Off)" + (lambda () (interactive) (diredp-dired-this-subdir t)) + :visible (and (cdr dired-subdir-alist) ; First is current dir. Must have at least one more. + (not (equal (expand-file-name (dired-current-directory)) + (expand-file-name default-directory)))) ; Must be sub, not top. + :help "Open Dired for subdir at or above point, tearing it off if inserted")) +(define-key diredp-menu-bar-single-menu [insert-subdir] + '(menu-item "Insert This Subdir" dired-maybe-insert-subdir + :visible (and (atom (diredp-this-subdir)) + (not (assoc (file-name-as-directory (diredp-this-subdir)) dired-subdir-alist))) + :enable (atom (diredp-this-subdir)) + :help "Insert a listing of this subdirectory")) +(define-key diredp-menu-bar-single-menu [goto-subdir] + '(menu-item "Go To Inserted Subdir" dired-maybe-insert-subdir + :visible (and (atom (diredp-this-subdir)) + (assoc (file-name-as-directory (diredp-this-subdir)) dired-subdir-alist)) + :enable (atom (diredp-this-subdir)) + :help "Go to the inserted listing of this subdirectory")) +(define-key diredp-menu-bar-single-menu [separator-subdir] '("--" ; ------------------------ + :visible (or (atom (diredp-this-subdir)) ; Subdir line. + (not (equal (expand-file-name (dired-current-directory)) + (expand-file-name default-directory)))))) ; Not top. + +(define-key diredp-menu-bar-single-menu [view] + '(menu-item "View (Read Only)" dired-view-file + :help "Examine file at cursor in read-only mode")) +(define-key diredp-menu-bar-single-menu [display] + '(menu-item "Display in Other Window" dired-display-file + :help "Display file at cursor in a different window")) + + +;; `Single' > `Open' menu. +;; +(defvar diredp-single-open-menu (make-sparse-keymap "Rename") + "`Open' submenu for Dired menu-bar `Single' menu.") +(define-key diredp-menu-bar-single-menu [multiple-open] (cons "Open" diredp-single-open-menu)) + +;; On Windows, bind more. +(eval-after-load "w32-browser" + '(progn + (define-key diredp-single-open-menu [dired-w32-browser] + '(menu-item "Open Associated Windows App" dired-w32-browser + :help "Open file using the Windows app associated with its file type")) + (define-key diredp-single-open-menu [dired-w32explore] + '(menu-item "Open in Windows Explorer" dired-w32explore + :help "Open file in Windows Explorer")))) +(define-key diredp-single-open-menu [find-file-other-frame] + '(menu-item "Open in Other Frame" diredp-find-file-other-frame + :help "Edit file at cursor in a different frame")) +(define-key diredp-single-open-menu [find-file-other-window] + '(menu-item "Open in Other Window" dired-find-file-other-window + :help "Edit file at cursor in a different window")) +(define-key diredp-single-open-menu [find-file] + '(menu-item "Open" dired-find-file :help "Edit file at cursor")) + + +;; `Single' > `Rename' menu. +;; +(defvar diredp-single-rename-menu (make-sparse-keymap "Rename") + "`Rename' submenu for Dired menu-bar `Single' menu.") +(define-key diredp-menu-bar-single-menu [multiple-case] (cons "Rename" diredp-single-rename-menu)) + +(define-key diredp-single-rename-menu [single-rename-capitalize] + '(menu-item "Capitalize" diredp-capitalize-this-file + :help "Capitalize (initial caps) name of file at cursor")) +(define-key diredp-single-rename-menu [single-rename-downcase] + '(menu-item "Downcase" diredp-downcase-this-file + ;; When running on plain MS-DOS, there is only one letter-case for file names. + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Rename file at cursor to a lower-case name")) +(define-key diredp-single-rename-menu [single-rename-upcase] + '(menu-item "Upcase" diredp-upcase-this-file + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Rename file at cursor to an upper-case name")) + + +;; `Single' > `Move / Copy / Link' menu. +;; +(defvar diredp-single-move-copy-link-menu (make-sparse-keymap "Move / Copy / Link") + "`Move / Copy / Link' submenu for Dired menu-bar `Single' menu.") +(define-key diredp-menu-bar-single-menu [multiple-move-copy-link] + (cons "Move / Copy / Link" diredp-single-move-copy-link-menu)) + +(define-key diredp-single-move-copy-link-menu [single-hardlink] + '(menu-item "Hardlink to..." diredp-hardlink-this-file + :help "Make hard links for current or marked files")) +(define-key diredp-single-move-copy-link-menu [single-symlink] + '(menu-item "Symlink to (Absolute)..." diredp-symlink-this-file + :help "Make absolute symbolic link for file at cursor")) +(define-key diredp-single-move-copy-link-menu [single-relsymlink] + '(menu-item "Symlink to (Relative)..." diredp-relsymlink-this-file + :help "Make relative symbolic link for file at cursor")) +(define-key diredp-single-move-copy-link-menu [single-copy] + '(menu-item "Copy to..." diredp-copy-this-file :help "Copy file at cursor")) +(define-key diredp-single-move-copy-link-menu [single-rename] + '(menu-item "Move to..." diredp-rename-this-file + :help "Rename file at cursor, or move it to a different directory")) + + +;; `Single' > `Image' menu. +;; +(defvar diredp-single-image-menu (make-sparse-keymap "Image")) +(defalias 'diredp-single-image-menu diredp-single-image-menu) +(define-key diredp-menu-bar-single-menu [image] + '(menu-item "Image" diredp-single-image-menu + :enable (let ((img-file (diredp-get-image-filename 'LOCALP 'NO-ERROR))) + (and (fboundp 'image-dired-dired-display-image) img-file)))) + +(define-key diredp-single-image-menu [diredp-image-dired-display-thumb] + '(menu-item "Go To Thumbnail" diredp-image-dired-display-thumb + :help "Pop to buffer showing the thumbnail of this image file")) +(define-key diredp-single-image-menu [diredp-image-dired-create-thumb] + '(menu-item "Create Thumbnail" diredp-image-dired-create-thumb + :help "Create a thumbnail image for this image file")) +(define-key diredp-single-image-menu [diredp-image-dired-edit-comment-and-tags] + '(menu-item "Edit Comment and Tags..." diredp-image-dired-edit-comment-and-tags + :help "Edit comment and tags for this image file")) +(define-key diredp-single-image-menu [diredp-image-dired-delete-tag] + '(menu-item "Delete Image Tag..." diredp-image-dired-delete-tag + :help "Remove an `image-dired' tag from this image file")) +(define-key diredp-single-image-menu [diredp-image-dired-tag-file] + '(menu-item "Add Tags..." diredp-image-dired-tag-file + :help "Add tags to this image file")) +(define-key diredp-single-image-menu [diredp-image-dired-comment-file] + '(menu-item "Add Comment..." diredp-image-dired-comment-file + :help "Add a comment to this image file")) +(define-key diredp-single-image-menu [diredp-image-dired-copy-with-exif-name] + '(menu-item "Copy with EXIF Name" diredp-image-dired-copy-with-exif-name + :help "Copy this image file to main image dir using EXIF name")) +(define-key diredp-single-image-menu [image-dired-dired-display-external] + '(menu-item "Display Externally" image-dired-dired-display-external + :help "Display image using external viewer")) +(define-key diredp-single-image-menu [image-dired-dired-display-image] + '(menu-item "Display to Fit Other Window" image-dired-dired-display-image + :help "Display scaled image to fit a separate window")) +(define-key diredp-single-image-menu [diredp-image-show-this-file] + '(menu-item "Display Full Size Or Smaller" diredp-image-show-this-file + :help "Display image full size or at least prefix-arg lines high")) +(define-key diredp-single-image-menu [dired-find-file] + '(menu-item "Display Full Size" dired-find-file + :help "Display image full size")) + + +;; `Single' > `Encryption' menu. +;; +(when (fboundp 'epa-dired-do-encrypt) ; Emacs 23+ + (defvar diredp-single-encryption-menu (make-sparse-keymap "Encryption")) + (define-key diredp-menu-bar-single-menu [encryption] + (cons "Encryption" diredp-single-encryption-menu)) + + (define-key diredp-single-encryption-menu [diredp-decrypt-this-file] + '(menu-item "Decrypt..." (lambda () + (interactive) + (epa-decrypt-file (expand-file-name (dired-get-filename + nil 'NO-ERROR-P)))) + :help "Decrypt this file")) + (define-key diredp-single-encryption-menu [diredp-verify-this-file] + '(menu-item "Verify..." (lambda () + (interactive) + (epa-verify-file (expand-file-name (dired-get-filename + nil 'NO-ERROR-P)))) + :help "Verify this file")) + (define-key diredp-single-encryption-menu [diredp-sign-this-file] + '(menu-item "Sign..." (lambda () + (interactive) + (epa-sign-file (expand-file-name (dired-get-filename + nil 'NO-ERROR-P)) + (epa-select-keys (epg-make-context) + "Select keys for signing. +If no one is selected, default secret key is used. " + nil t))) + :help "Encrypt this file")) + (define-key diredp-single-encryption-menu [diredp-encrypt-this-file] + '(menu-item "Encrypt..." (lambda () + (interactive) + (epa-encrypt-file (expand-file-name (dired-get-filename + nil 'NO-ERROR-P)) + (epa-select-keys + (epg-make-context) + "Select recipients for encryption. +If no one is selected, symmetric encryption will be performed. " + nil t))) + :help "Sign this file"))) + + +;; `Single' > `Bookmark' menu. +;; +(when (require 'bookmark+ nil t) + (defvar diredp-single-bookmarks-menu (make-sparse-keymap "Bookmark")) + (define-key diredp-menu-bar-single-menu [bookmark] + (cons "Bookmark" diredp-single-bookmarks-menu)) + + (define-key diredp-single-bookmarks-menu [diredp-set-tag-value-this-file] + '(menu-item "Set Tag Value..." diredp-set-tag-value-this-file + :help "Set the value (not the name) of a given tag for this file")) + (define-key diredp-single-bookmarks-menu [diredp-paste-replace-tags-this-file] + '(menu-item "Paste Tags (Replace)" diredp-paste-replace-tags-this-file + :help "Replace tags for this file with previously copied tags")) + (define-key diredp-single-bookmarks-menu [diredp-paste-add-tags-this-file] + '(menu-item "Paste Tags (Add)" diredp-paste-add-tags-this-file + :help "Add previously copied tags to this file")) + (define-key diredp-single-bookmarks-menu [diredp-copy-tags-this-file] + '(menu-item "Copy Tags" diredp-copy-tags-this-file + :help "Copy the tags from this file, so you can paste them to another")) + (define-key diredp-single-bookmarks-menu [diredp-remove-all-tags-this-file] + '(menu-item "Remove All Tags" diredp-remove-all-tags-this-file + :help "Remove all tags from the file at cursor")) + (define-key diredp-single-bookmarks-menu [diredp-untag-this-file] + '(menu-item "Remove Tags..." diredp-untag-this-file + :help "Remove some tags from the file at cursor (`C-u': remove all tags)")) + (define-key diredp-single-bookmarks-menu [diredp-tag-this-file] + '(menu-item "Add Tags..." diredp-tag-this-file :help "Add some tags to the file at cursor")) + (define-key diredp-single-bookmarks-menu [diredp-bookmark-this-file] + '(menu-item "Bookmark..." diredp-bookmark-this-file + :help "Bookmark the file at cursor (create/set autofile)"))) + + +;; `Multiple' menu. +;; +;; REPLACE ORIGINAL "Operate" menu in `dired.el'. +;; +(defvar diredp-menu-bar-multiple-menu (make-sparse-keymap "Multiple")) +(define-key dired-mode-map [menu-bar operate] (cons "Multiple" diredp-menu-bar-multiple-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-operate-menu 'diredp-menu-bar-multiple-menu)) +(make-obsolete-variable 'diredp-menu-bar-operate-menu 'diredp-menu-bar-multiple-menu) ; 2017-04-09 + +(define-key diredp-menu-bar-multiple-menu [diredp-describe-marked-autofiles] + '(menu-item "Describe Marked Autofiles" diredp-describe-marked-autofiles + :help "Show the metadata for the marked files that are autofiles" + :enable (featurep 'bookmark+))) +(define-key diredp-menu-bar-multiple-menu [separator-describe] '("--")) ; ----------------------- + +(unless (memq system-type '(windows-nt ms-dos)) + (define-key diredp-menu-bar-multiple-menu [chown] + '(menu-item "Change Owner..." dired-do-chown + :help "Change the owner of marked files"))) +(unless (memq system-type '(windows-nt ms-dos)) + (define-key diredp-menu-bar-multiple-menu [chgrp] + '(menu-item "Change Group..." dired-do-chgrp + :help "Change the owner of marked files"))) +(define-key diredp-menu-bar-multiple-menu [chmod] + '(menu-item "Change Mode..." dired-do-chmod + :help "Change mode (attributes) of marked files")) +(when (fboundp 'dired-do-touch) ; Emacs 22+ + (define-key diredp-menu-bar-multiple-menu [touch] + '(menu-item "Change Timestamp (`touch')..." dired-do-touch + :help "Change the timestamp of the marked files, using `touch'"))) +(define-key diredp-menu-bar-multiple-menu [separator-change] '("--")) ; ------------------------- + +(when (fboundp 'diredp-read-expression) ; Emacs 22+ + (define-key diredp-menu-bar-multiple-menu [diredp-do-lisp-sexp] + '(menu-item "Eval Sexp..." diredp-do-lisp-sexp + :help "Evaluate an Emacs-Lisp sexp in each marked file"))) +(define-key diredp-menu-bar-multiple-menu [diredp-do-emacs-command] + '(menu-item "Invoke Emacs Command..." diredp-do-emacs-command + :help "Invoke an Emacs command in each marked file")) +(define-key diredp-menu-bar-multiple-menu [diredp-do-apply-function] + '(menu-item "Apply Function..." diredp-do-apply-function + :help "Apply a Lisp function to each marked file name (`C-u': file contents, not name)")) +(define-key diredp-menu-bar-multiple-menu [print] + '(menu-item "Print..." dired-do-print :help "Print marked files, supplying print command")) +(define-key diredp-menu-bar-multiple-menu [compress] + '(menu-item "Compress/Uncompress" dired-do-compress :help "Compress/uncompress marked files")) +(when (fboundp 'dired-do-compress-to) + (define-key diredp-menu-bar-multiple-menu [compress-to] + '(menu-item "Compress to..." dired-do-compress-to + :help "Compress marked files and dirs together, in the same archive"))) +(define-key diredp-menu-bar-multiple-menu [command] + '(menu-item "Shell Command..." dired-do-shell-command + :help "Run a shell command on each marked file")) +(when (fboundp 'dired-do-async-shell-command) ; Emacs 23+ + (define-key diredp-menu-bar-multiple-menu [async-command] + '(menu-item "Asynchronous Shell Command..." dired-do-async-shell-command + :help "Run a shell command asynchronously on each marked file"))) +(define-key diredp-menu-bar-multiple-menu [compile] + '(menu-item "Byte Compile" dired-do-byte-compile :help "Byte-compile marked Emacs Lisp files")) +(define-key diredp-menu-bar-multiple-menu [load] + '(menu-item "Load" dired-do-load :help "Load marked Emacs Lisp files")) + +(unless (require 'bookmark+ nil t) + (define-key diredp-menu-bar-multiple-menu [diredp-bookmark-this-file] + '(menu-item "Bookmark..." diredp-bookmark-this-file :help "Bookmark the file at cursor"))) +(when (fboundp 'mkhtml-dired-files) ; In `mkhtml.el'. + (define-key diredp-menu-bar-multiple-menu [mkhtml-dired-files] + '(menu-item "Create HTML" mkhtml-dired-files + :help "Create HTML files corresponding to marked files"))) +(define-key diredp-menu-bar-multiple-menu [separator-misc] '("--")) ; --------------------------- + +(define-key diredp-menu-bar-multiple-menu [diredp-copy-abs-filenames-as-kill] + '(menu-item "Copy Marked Names as Absolute" diredp-copy-abs-filenames-as-kill + :help "Copy absolute names of marked files to the kill ring" + :keys "M-0 w")) +(define-key diredp-menu-bar-multiple-menu [kill-ring] + '(menu-item "Copy Marked Names" dired-copy-filename-as-kill + :help "Copy names of marked files to the kill ring, for pasting")) +(define-key diredp-menu-bar-multiple-menu [diredp-list-marked] + '(menu-item "List Marked Files" diredp-list-marked + :help "List the files marked here (C-u C-u: all, C-u C-u C-u: all + dirs)")) +(define-key diredp-menu-bar-multiple-menu [diredp-insert-subdirs] + '(menu-item "Insert Subdirs" diredp-insert-subdirs + :help "Insert the marked subdirectories - like using `i' at each marked dir")) +;; On Windows, bind more. +(eval-after-load "w32-browser" + '(define-key diredp-menu-bar-multiple-menu [dired-multiple-w32-browser] + '(menu-item "Open Associated Windows Apps" dired-multiple-w32-browser + :help "Open files using the Windows apps associated with their file types"))) +(when (fboundp 'dired-do-find-marked-files) + (define-key diredp-menu-bar-multiple-menu [find-files] + '(menu-item "Open" dired-do-find-marked-files ; In `dired-x.el'. + :help "Open each marked file for editing"))) + + +;; `Multiple' > `Dired' menu. +;; +(defvar diredp-multiple-dired-menu (make-sparse-keymap "Dired") + "`Dired' submenu for Dired menu-bar `Multiple' menu.") +(define-key diredp-menu-bar-multiple-menu [multiple-dired] + `(menu-item "Dired" ,diredp-multiple-dired-menu + :enable (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t))) + :help "Open Dired on marked files and dirs only")) + +(define-key diredp-multiple-dired-menu [diredp-marked-other-window] + '(menu-item "Dired Marked in Other Window" diredp-marked-other-window + :enable (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t))) + :help "Open Dired on marked files and dirs only, in other window")) +(define-key diredp-multiple-dired-menu [diredp-marked] + '(menu-item "Dired Marked" diredp-marked + :enable (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t))) + :help "Open Dired on marked files and dirs only")) + + +;; `Multiple' > `Omit' menu. +;; +(defvar diredp-multiple-omit-menu (make-sparse-keymap "Omit") + "`Omit' submenu for Dired menu-bar `Multiple' menu.") +(define-key diredp-menu-bar-multiple-menu [multiple-omit] (cons "Omit" diredp-multiple-omit-menu)) + +(define-key diredp-multiple-omit-menu [omit-unmarked] + '(menu-item "Omit Unmarked" diredp-omit-unmarked :help "Hide lines of unmarked files")) +(define-key diredp-multiple-omit-menu [omit-marked] + '(menu-item "Omit Marked" diredp-omit-marked :help "Hide lines of marked files")) + + +;; `Multiple' > `Delete' menu. +;; +(defvar diredp-multiple-delete-menu (make-sparse-keymap "Delete") + "`Delete' submenu for Dired menu-bar `Multiple' menu.") +(define-key diredp-menu-bar-multiple-menu [multiple-delete] (cons "Delete" diredp-multiple-delete-menu)) + +(define-key diredp-multiple-delete-menu [delete-flagged] + '(menu-item "Delete Flagged" dired-do-flagged-delete + :help "Delete all files flagged for deletion (D)")) +(define-key diredp-multiple-delete-menu [delete] + '(menu-item "Delete Marked (not Flagged)" dired-do-delete + :help "Delete current file or all marked files (not flagged files)")) + + +;; `Multiple' > `Rename' menu. +;; +(defvar diredp-multiple-rename-menu (make-sparse-keymap "Rename") + "`Rename' submenu for Dired menu-bar `Multiple' menu.") +(define-key diredp-menu-bar-multiple-menu [multiple-case] (cons "Rename" diredp-multiple-rename-menu)) + +(define-key diredp-multiple-rename-menu [multiple-rename-rename] + '(menu-item "Move to Dir... / Rename This..." dired-do-rename + :help "Move marked (or next N) files, or rename current file")) + +(define-key diredp-multiple-rename-menu [multiple-rename-capitalize] + '(menu-item "Capitalize" diredp-capitalize + :help "Capitalize (initial caps) the names of all marked files")) +(define-key diredp-multiple-rename-menu [multiple-rename-downcase] + '(menu-item "Downcase" dired-downcase + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Rename marked files to lowercase names")) +(define-key diredp-multiple-rename-menu [multiple-rename-upcase] + '(menu-item "Upcase" dired-upcase + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Rename marked files to uppercase names")) + + +;; `Multiple' > `Move / Copy / Link' menu. +;; +(defvar diredp-multiple-move-copy-link-menu (make-sparse-keymap "Move / Copy / Link") + "`Move / Copy / Link' submenu for Dired menu-bar `Multiple' menu.") +(define-key diredp-menu-bar-multiple-menu [multiple-move-copy-link] + (cons "Move / Copy / Link" diredp-multiple-move-copy-link-menu)) + +(define-key diredp-multiple-move-copy-link-menu [multiple-move-copy-link-hardlink] + '(menu-item "Hardlink to..." dired-do-hardlink + :help "Make hard links for current or marked files")) +(define-key diredp-multiple-move-copy-link-menu [multiple-move-copy-link-symlink] + '(menu-item "Symlink to (Absolute)..." dired-do-symlink ; In `dired-aux.el'. + :help "Make absolute symbolic links for current or marked files")) +(define-key diredp-multiple-move-copy-link-menu [multiple-move-copy-link-relsymlink] + '(menu-item "Symlink to (Relative)..." dired-do-relsymlink ; In `dired-x.el'. + :help "Make relative symbolic links for current or marked files")) +(define-key diredp-multiple-move-copy-link-menu [multiple-move-copy-link-copy] + '(menu-item "Copy to..." dired-do-copy :help "Copy current file or all marked files")) +(define-key diredp-multiple-move-copy-link-menu [multiple-move-copy-link-rename] + '(menu-item "Move to..." dired-do-rename :help "Rename current file or move marked files")) + + +;; `Multiple' > `Images' menu. +;; +(defvar diredp-multiple-images-menu (make-sparse-keymap "Images")) +(defalias 'diredp-multiple-images-menu diredp-multiple-images-menu) +(define-key diredp-menu-bar-multiple-menu [images] + '(menu-item "Images" diredp-multiple-images-menu + :enable (fboundp 'image-dired-display-thumbs))) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-images-menu 'diredp-multiple-images-menu)) +(make-obsolete-variable 'diredp-menu-bar-images-menu 'diredp-multiple-images-menu) ; 2017-04-09 + +;; Remove the items from `Multiple' menu. +(define-key diredp-menu-bar-multiple-menu [image-dired-delete-tag] nil) +(define-key diredp-menu-bar-multiple-menu [image-dired-tag-files] nil) +(define-key diredp-menu-bar-multiple-menu [image-dired-dired-comment-files] nil) +(define-key diredp-menu-bar-multiple-menu [image-dired-display-thumbs] nil) + +;; Add them to `Multiple' > `Images' menu. +(define-key diredp-multiple-images-menu [image-dired-delete-tag] + '(menu-item "Delete Tag..." image-dired-delete-tag + :help "Delete tag from marked image files")) +(define-key diredp-multiple-images-menu [image-dired-tag-files] + '(menu-item "Add Tags..." image-dired-tag-files + :help "Add tags to marked image files")) +(define-key diredp-multiple-images-menu [image-dired-dired-comment-files] + '(menu-item "Add Comment..." image-dired-dired-comment-files + :help "Add comment to marked image files")) +(define-key diredp-multiple-images-menu [image-dired-display-thumbs] + '(menu-item "Display Thumbnails" image-dired-display-thumbs + :help "Display thumbnails for marked image files")) +(define-key diredp-multiple-images-menu [diredp-do-display-images] + '(menu-item "Display" diredp-do-display-images + :help "Display the marked image files")) + + +;; `Multiple' > `Encryption' menu. +;; +(when (fboundp 'epa-dired-do-encrypt) ; Emacs 23+ + (defvar diredp-multiple-encryption-menu (make-sparse-keymap "Encryption")) + (define-key diredp-menu-bar-multiple-menu [encryption] + (cons "Encryption" diredp-multiple-encryption-menu)) + + ;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs + ;; works for newer Emacs too. + (when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-encryption-menu 'diredp-multiple-encryption-menu)) + (make-obsolete-variable 'diredp-menu-bar-encryption-menu 'diredp-multiple-encryption-menu) ; 2017-04-09 + + (when (boundp 'diredp-menu-bar-encryption-menu) + (defalias 'diredp-menu-bar-encryption-menu diredp-menu-bar-encryption-menu)) + (make-obsolete 'diredp-menu-bar-encryption-menu 'diredp-multiple-encryption-menu) ; 2017-04-09 + + ;; Remove the items from `Multiple' menu. + (define-key diredp-menu-bar-multiple-menu [epa-dired-do-decrypt] nil) + (define-key diredp-menu-bar-multiple-menu [epa-dired-do-verify] nil) + (define-key diredp-menu-bar-multiple-menu [epa-dired-do-sign] nil) + (define-key diredp-menu-bar-multiple-menu [epa-dired-do-encrypt] nil) + + ;; Add them to `Multiple' > `Encryption' menu. + (define-key diredp-multiple-encryption-menu [epa-dired-do-decrypt] + '(menu-item "Decrypt..." epa-dired-do-decrypt :help "Decrypt the marked files")) + (define-key diredp-multiple-encryption-menu [epa-dired-do-verify] + '(menu-item "Verify..." epa-dired-do-verify :help "Verify the marked files")) + (define-key diredp-multiple-encryption-menu [epa-dired-do-sign] + '(menu-item "Sign..." epa-dired-do-sign :help "Sign the marked files")) + (define-key diredp-multiple-encryption-menu [epa-dired-do-encrypt] + '(menu-item "Encrypt..." epa-dired-do-encrypt :help "Encrypt the marked files"))) + + +;; `Multiple' > `Search' menu. +;; +(defvar diredp-multiple-search-menu (make-sparse-keymap "Search")) +(define-key diredp-menu-bar-multiple-menu [search] + (cons "Search" diredp-multiple-search-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-operate-search-menu 'diredp-multiple-search-menu)) +(make-obsolete-variable 'diredp-menu-bar-operate-search-menu 'diredp-multiple-search-menu) ; 2017-04-09 + +(when (fboundp 'dired-do-isearch-regexp) ; Emacs 23+ + (define-key diredp-multiple-search-menu [isearch-regexp] + '(menu-item "Isearch Regexp Files..." dired-do-isearch-regexp + :help "Incrementally search marked files for regexp")) + (define-key diredp-multiple-search-menu [isearch] + '(menu-item "Isearch Files..." dired-do-isearch + :help "Incrementally search marked files for string"))) +(when (fboundp 'dired-do-find-regexp-and-replace) + (define-key diredp-multiple-search-menu [find-query-replace] + '(menu-item "Query Replace Using `find'..." dired-do-find-regexp-and-replace + :help "Replace regexp in marked files using `find'"))) +(define-key diredp-multiple-search-menu [query-replace] + (if (< emacs-major-version 21) + '(menu-item "Query Replace Using TAGS Table..." dired-do-query-replace) + '(menu-item "Query Replace Using TAGS Table..." dired-do-query-replace-regexp + :help "Replace regexp in marked files using tags in a TAGS table"))) +(when (fboundp 'dired-do-find-regexp) + (define-key diredp-multiple-search-menu [find-regexp] + '(menu-item "Search Files Using `find'..." dired-do-find-regexp + :help "Search marked files for regexp using `find'"))) +(define-key diredp-multiple-search-menu [search] + '(menu-item "Search Files Using TAGS Table..." dired-do-search + :help "Search marked files for regexp using tags in a TAGS table")) +(define-key diredp-multiple-search-menu [grep] + '(menu-item "Grep..." diredp-do-grep :help "Grep marked, next N, or all files shown")) + + +;; `Multiple' > `Bookmark' menu. +;; +(defvar diredp-multiple-bookmarks-menu (make-sparse-keymap "Bookmark")) +(define-key diredp-menu-bar-multiple-menu [bookmark] + (cons "Bookmark" diredp-multiple-bookmarks-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-operate-bookmarks-menu 'diredp-multiple-bookmarks-menu)) +(make-obsolete-variable 'diredp-menu-bar-operate-bookmarks-menu 'diredp-multiple-bookmarks-menu) ; 2017-04-09 + +(when (require 'bookmark+ nil t) + (define-key diredp-multiple-bookmarks-menu [diredp-do-set-tag-value] + '(menu-item "Set Tag Value..." diredp-do-set-tag-value + :help "Set the value of a given tag for the marked or next N files")) + (define-key diredp-multiple-bookmarks-menu [diredp-do-paste-replace-tags] + '(menu-item "Paste Tags (Replace)" diredp-do-paste-replace-tags + :help "Replace tags for the marked or next N files with copied tags")) + (define-key diredp-multiple-bookmarks-menu [diredp-do-paste-add-tags] + '(menu-item "Paste Tags (Add)" diredp-do-paste-add-tags + :help "Add previously copied tags to the marked or next N files")) + (define-key diredp-multiple-bookmarks-menu [diredp-do-remove-all-tags] + '(menu-item "Remove All Tags" diredp-do-remove-all-tags + :help "Remove all tags from the marked or next N files")) + (define-key diredp-multiple-bookmarks-menu [diredp-do-untag] + '(menu-item "Remove Tags..." diredp-do-untag + :help "Remove some tags from the marked or next N files")) + (define-key diredp-multiple-bookmarks-menu [diredp-do-tag] + '(menu-item "Add Tags..." diredp-do-tag + :help "Add some tags to the marked or next N files")) + (define-key diredp-multiple-bookmarks-menu [separator-book-2] '("--"))) ; ------------ + +(define-key diredp-multiple-bookmarks-menu + [diredp-do-bookmark-in-bookmark-file-recursive] + '(menu-item "Bookmark in Bookmark File (Here and Below)..." + diredp-do-bookmark-in-bookmark-file-recursive + :help "Bookmark marked files (including in marked subdirs) in bookmark file and save it")) +(define-key diredp-multiple-bookmarks-menu + [diredp-set-bookmark-file-bookmark-for-marked-recursive] + '(menu-item "Create Bookmark-File Bookmark (Here and Below)..." + diredp-set-bookmark-file-bookmark-for-marked-recursive + :help "Create a bookmark-file bookmark for marked files, including in marked subdirs")) +(define-key diredp-multiple-bookmarks-menu [diredp-do-bookmark-dirs-recursive] + '(menu-item "Bookmark Dirs (Here and Below)..." diredp-do-bookmark-dirs-recursive + :help "Bookmark this Dired buffer and marked subdirectory Dired buffers, recursively.")) +(define-key diredp-multiple-bookmarks-menu [diredp-do-bookmark-recursive] + '(menu-item "Bookmark (Here and Below)..." diredp-do-bookmark-recursive + :help "Bookmark the marked files, including those in marked subdirs")) +(define-key diredp-multiple-bookmarks-menu [separator-book-1] '("--")) ; --------------- + +(define-key diredp-multiple-bookmarks-menu [diredp-do-bookmark-in-bookmark-file] + '(menu-item "Bookmark in Bookmark File..." diredp-do-bookmark-in-bookmark-file + :help "Bookmark the marked files in BOOKMARK-FILE and save BOOKMARK-FILE")) +(define-key diredp-multiple-bookmarks-menu [diredp-set-bookmark-file-bookmark-for-marked] + '(menu-item "Create Bookmark-File Bookmark..." diredp-set-bookmark-file-bookmark-for-marked + :help "Create a bookmark-file bookmark, and bookmark the marked files in it")) +(define-key diredp-multiple-bookmarks-menu [diredp-do-bookmark] + '(menu-item "Bookmark..." diredp-do-bookmark :help "Bookmark the marked or next N files")) + + +;; `Multiple' > `Marked Here and Below' menu. +;; +(defvar diredp-multiple-recursive-menu (make-sparse-keymap "Marked Here and Below")) +(define-key diredp-menu-bar-multiple-menu [operate-recursive] + (cons "Marked Here and Below" diredp-multiple-recursive-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-operate-recursive-menu 'diredp-multiple-recursive-menu)) +(make-obsolete-variable 'diredp-menu-bar-operate-recursive-menu 'diredp-multiple-recursive-menu) ; 2017-04-09 + +(when (fboundp 'diredp-do-chown-recursive) + (define-key diredp-multiple-recursive-menu [chown] + '(menu-item "Change Owner..." diredp-do-chown-recursive + :help "Change the owner of marked files, including those in marked subdirs"))) +(when (fboundp 'diredp-do-chgrp-recursive) + (define-key diredp-multiple-recursive-menu [chgrp] + '(menu-item "Change Group..." diredp-do-chgrp-recursive + :help "Change the owner of marked files, including those in marked subdirs"))) +(define-key diredp-multiple-recursive-menu [chmod] + '(menu-item "Change Mode..." diredp-do-chmod-recursive + :help "Change mode (attributes) of marked files, including those in marked subdirs")) +(when (fboundp 'dired-do-touch) ; Emacs 22+ + (define-key diredp-multiple-recursive-menu [touch] + '(menu-item "Change Timestamp (`touch')..." diredp-do-touch-recursive + :help "Change timestamp of marked files, including those in marked subdirs"))) +(define-key diredp-multiple-recursive-menu [separator-change] '("--")) ; ---------------- + +(define-key diredp-multiple-recursive-menu [diredp-do-apply-function-recursive] + '(menu-item "Apply Lisp Function..." diredp-do-apply-function-recursive + :help "Apply a Lisp function to the marked files, including those in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-do-print-recursive] + '(menu-item "Print..." diredp-do-print-recursive + :help "Print the marked files, including those in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-do-shell-command-recursive] + '(menu-item "Shell Command..." diredp-do-shell-command-recursive + :help "Run shell command on the marked files, including those in marked subdirs")) +(when (fboundp 'dired-do-async-shell-command) ; Emacs 23+ + (define-key diredp-multiple-recursive-menu [diredp-do-async-shell-command-recursive] + '(menu-item "Asynchronous Shell Command..." diredp-do-async-shell-command-recursive + :help "Run shell command asynchronously on marked files, including in marked subdirs"))) + +(when (fboundp 'diredp-unmark-all-marks-recursive) ; Emacs 22+ + (define-key diredp-multiple-recursive-menu [separator-1] '("--")) ; ------------ + (define-key diredp-multiple-recursive-menu [diredp-change-marks-recursive] + '(menu-item "Change Mark..." diredp-change-marks-recursive + :help "Change all OLD marks to NEW marks, including those in marked subdirs")) + (define-key diredp-multiple-recursive-menu [diredp-unmark-all-files-recursive] + '(menu-item "Unmark Marked-With..." diredp-unmark-all-files-recursive + :help "Remove a given mark everywhere, including in marked subdirs")) + (define-key diredp-multiple-recursive-menu [diredp-unmark-all-marks-recursive] + '(menu-item "Unmark All..." diredp-unmark-all-marks-recursive + :help "Remove ALL marks everywhere, including in marked subdirs"))) + +(define-key diredp-multiple-recursive-menu [separator-misc] '("--")) ; ------------------ + +(define-key diredp-multiple-recursive-menu [diredp-do-delete-recursive] + '(menu-item "Delete Marked (not Flagged)" diredp-do-delete-recursive + :help "Delete marked (not flagged) files, including in marked subdirs")) +(define-key diredp-multiple-recursive-menu [separator-delete] '("--")) ; ---------------- + +(define-key diredp-multiple-recursive-menu [diredp-do-hardlink-recursive] + '(menu-item "Hardlink to..." diredp-do-hardlink-recursive + :help "Make hard links for marked files, including those in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-do-symlink-recursive] + '(menu-item "Symlink to (Absolute)..." diredp-do-symlink-recursive + :help "Make absolute symbolic links for marked files, including those in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-do-relsymlink-recursive] + '(menu-item "Symlink to (Relative)..." diredp-do-relsymlink-recursive + :help "Make relative symbolic links for marked files, including those in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-do-copy-recursive] + '(menu-item "Copy to..." diredp-do-copy-recursive + :help "Copy marked files, including in marked subdirs, to a given directory")) +(define-key diredp-multiple-recursive-menu [diredp-do-move-recursive] + '(menu-item "Move to..." diredp-do-move-recursive + :help "Move marked files, including in marked subdirs, to a given directory")) +(define-key diredp-multiple-recursive-menu [separator-copy-move] '("--")) ; ------------- + +(define-key diredp-multiple-recursive-menu [diredp-capitalize-recursive] + '(menu-item "Capitalize" diredp-capitalize-recursive + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Capitalize the names of all marked files, including in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-downcase-recursive] + '(menu-item "Downcase" diredp-downcase-recursive + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Rename marked files, including in marked subdirs, to lowercase names")) +(define-key diredp-multiple-recursive-menu [diredp-upcase-recursive] + '(menu-item "Upcase" diredp-upcase-recursive + :enable (or (not (fboundp 'msdos-long-file-names)) (msdos-long-file-names)) + :help "Rename marked files, including in marked subdirs, to uppercase names")) +(define-key diredp-multiple-recursive-menu [separator-lettercase] '("--")) ; ------------ + +(define-key diredp-multiple-recursive-menu [diredp-list-marked-recursive] + '(menu-item "List Marked Files" diredp-list-marked-recursive + :help "List the files marked here and in marked subdirs, recursively")) +(define-key diredp-multiple-recursive-menu [diredp-copy-filename-as-kill-recursive] + '(menu-item "Copy File Names (to Paste)" diredp-copy-filename-as-kill-recursive + :help "Copy names of files marked here and in marked subdirs, to `kill-ring'")) +(define-key diredp-multiple-recursive-menu [diredp-insert-subdirs-recursive] + '(menu-item "Insert Subdirs" diredp-insert-subdirs-recursive + :help "Insert the marked subdirectories, gathered recursively")) +(define-key diredp-multiple-recursive-menu [separator-dirs] '("--")) ; ------------------ + +(define-key diredp-multiple-recursive-menu [diredp-marked-recursive-other-window] + '(menu-item "Dired (Marked) in Other Window" diredp-marked-recursive-other-window + :help "Open Dired (in other window) on marked files, including those in marked subdirs")) +(define-key diredp-multiple-recursive-menu [diredp-marked-recursive] + '(menu-item "Dired (Marked)" diredp-marked-recursive + :help "Open Dired on marked files, including those in marked subdirs")) +;; On Windows, bind more. +(eval-after-load "w32-browser" + '(define-key diredp-multiple-recursive-menu [diredp-multiple-w32-browser-recursive] + '(menu-item "Open Associated Windows Apps" diredp-multiple-w32-browser-recursive + :help "Run Windows apps for with marked files, including those in marked subdirs"))) +(define-key diredp-multiple-recursive-menu [diredp-do-find-marked-files-recursive] + '(menu-item "Open" diredp-do-find-marked-files-recursive + :help "Find marked files simultaneously, including those in marked subdirs")) + + +;; `Multiple' > `Marked Here and Below' > `Images' menu. +;; +(defvar diredp-images-recursive-menu (make-sparse-keymap "Images")) +(defalias 'diredp-images-recursive-menu diredp-images-recursive-menu) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-images-recursive-menu 'diredp-images-recursive-menu)) +(make-obsolete-variable 'diredp-menu-bar-images-recursive-menu 'diredp-images-recursive-menu) ; 2017-04-09 + +(when (boundp 'diredp-menu-bar-images-recursive-menu) + (defalias 'diredp-menu-bar-images-recursive-menu diredp-menu-bar-images-recursive-menu)) +(make-obsolete 'diredp-menu-bar-images-recursive-menu 'diredp-images-recursive-menu) ; 2017-04-09 + +(define-key diredp-multiple-recursive-menu [images] + '(menu-item "Images" diredp-images-recursive-menu + :enable (fboundp 'image-dired-delete-tag))) +(define-key diredp-images-recursive-menu [diredp-image-dired-delete-tag-recursive] + '(menu-item "Delete Image Tag..." diredp-image-dired-delete-tag-recursive + :help "Remove an `image-dired' tag from marked files, including those in marked subdirs")) +(define-key diredp-images-recursive-menu [diredp-image-dired-tag-files-recursive] + '(menu-item "Add Image Tags..." diredp-image-dired-tag-files-recursive + :help "Add `image-dired' tags to marked files, including those in marked subdirs")) +(define-key diredp-images-recursive-menu [diredp-image-dired-comment-files-recursive] + '(menu-item "Add Image Comment..." diredp-image-dired-comment-files-recursive + :help "Add image comment to marked files, including those in marked subdirs")) +(define-key diredp-images-recursive-menu [diredp-image-dired-display-thumbs-recursive] + '(menu-item "Display Image Thumbnails" diredp-image-dired-display-thumbs-recursive + :help "Show thumbnails for marked image files, including those in marked subdirs")) + + +;; `Multiple' > `Marked Here and Below' > `Encryption' menu. +;; +(when (fboundp 'epa-dired-do-encrypt) ; Emacs 23+ + (defvar diredp-menu-bar-encryption-recursive-menu (make-sparse-keymap "Encryption")) + (define-key diredp-multiple-recursive-menu [encryption] + (cons "Encryption" diredp-menu-bar-encryption-recursive-menu)) + (define-key diredp-menu-bar-encryption-recursive-menu [diredp-do-decrypt-recursive] + '(menu-item "Decrypt..." diredp-do-decrypt-recursive + :help "Decrypt marked files, including those in marked subdirs")) + (define-key diredp-menu-bar-encryption-recursive-menu [diredp-do-verify-recursive] + '(menu-item "Verify..." diredp-do-verify-recursive + :help "Verify marked files, including those in marked subdirs")) + (define-key diredp-menu-bar-encryption-recursive-menu [diredp-do-sign-recursive] + '(menu-item "Sign..." diredp-do-sign-recursive + :help "Sign marked files, including those in marked subdirs")) + (define-key diredp-menu-bar-encryption-recursive-menu [diredp-do-encrypt-recursive] + '(menu-item "Encrypt..." diredp-do-encrypt-recursive + :help "Encrypt marked files, including those in marked subdirs"))) + + +;; `Multiple' > `Marked Here and Below' > `Search' menu. +;; +(defvar diredp-menu-bar-search-recursive-menu (make-sparse-keymap "Search")) +(define-key diredp-multiple-recursive-menu [search] + (cons "Search" diredp-menu-bar-search-recursive-menu)) +(when (fboundp 'dired-do-isearch-regexp) ; Emacs 23+ + (define-key diredp-menu-bar-search-recursive-menu [diredp-do-isearch-regexp-recursive] + '(menu-item "Isearch Regexp Files..." diredp-do-isearch-regexp-recursive + :help "Incrementally regexp search marked files, including those in marked subdirs")) + (define-key diredp-menu-bar-search-recursive-menu [diredp-do-isearch-recursive] + '(menu-item "Isearch Files..." diredp-do-isearch-recursive + :help "Incrementally search marked files, including those in marked subdirs"))) +(define-key diredp-menu-bar-search-recursive-menu [diredp-do-query-replace-regexp-recursive] + '(menu-item "Query Replace..." diredp-do-query-replace-regexp-recursive + :help "Replace regexp in marked files, including those in marked subdirs")) +(define-key diredp-menu-bar-search-recursive-menu [diredp-do-search-recursive] + '(menu-item "Search Files..." diredp-do-search-recursive + :help "Regexp search marked files, including those in marked subdirs")) +(define-key diredp-menu-bar-search-recursive-menu [diredp-do-grep-recursive] + '(menu-item "Grep..." diredp-do-grep-recursive + :help "Run `grep' on the marked files, including those in marked subdirs")) + + +;; `Multiple' > `Marked Here and Below' > `Bookmark' menu. +;; +(defvar diredp-menu-bar-bookmarks-recursive-menu (make-sparse-keymap "Bookmark")) +(define-key diredp-multiple-recursive-menu [bookmarks] + (cons "Bookmark" diredp-menu-bar-bookmarks-recursive-menu)) +(define-key diredp-menu-bar-bookmarks-recursive-menu + [diredp-do-bookmark-in-bookmark-file-recursive] + '(menu-item "Bookmark in Bookmark File..." diredp-do-bookmark-in-bookmark-file-recursive + :help "Bookmark marked files, including those in marked subdirs, in a bookmark file")) +(define-key diredp-menu-bar-bookmarks-recursive-menu + [diredp-set-bookmark-file-bookmark-for-marked-recursive] + '(menu-item "Create Bookmark-File Bookmark..." + diredp-set-bookmark-file-bookmark-for-marked-recursive + :help "Create a bookmark-file bookmark for marked files, including in marked subdirs")) +(define-key diredp-menu-bar-bookmarks-recursive-menu [diredp-do-bookmark-dirs-recursive] + '(menu-item "Bookmark Dirs..." diredp-do-bookmark-dirs-recursive + :help "Bookmark this Dired buffer and marked subdirectory Dired buffers, recursively.")) +(define-key diredp-menu-bar-bookmarks-recursive-menu [diredp-do-bookmark-recursive] + '(menu-item "Bookmark..." diredp-do-bookmark-recursive + :help "Bookmark the marked files, including those in marked subdirs")) + + + +;; `Regexp' menu. +;; +;; REPLACE ORIGINAL `Regexp' menu in `dired.el'. +;; +(defvar diredp-menu-bar-regexp-menu (make-sparse-keymap "Regexp")) +(define-key dired-mode-map [menu-bar regexp] (cons "Regexp" diredp-menu-bar-regexp-menu)) + +(define-key diredp-menu-bar-regexp-menu [hardlink] + '(menu-item "Hardlink to..." dired-do-hardlink-regexp ; In `dired-aux.el'. + :help "Make hard links for files matching regexp")) +(define-key diredp-menu-bar-regexp-menu [symlink] + '(menu-item "Symlink to (Absolute)..." dired-do-symlink-regexp ; In `dired-aux.el'. + :help "Make absolute symbolic links for files matching regexp")) +(define-key diredp-menu-bar-regexp-menu [relsymlink] + '(menu-item "Symlink to (Relative)..." dired-do-relsymlink-regexp ; In `dired-x.el'. + :help "Make relative symbolic links for files matching regexp")) +(define-key diredp-menu-bar-regexp-menu [copy] + '(menu-item "Copy to..." dired-do-copy-regexp ; In `dired-aux.el'. + :help "Copy marked files matching regexp")) +(define-key diredp-menu-bar-regexp-menu [rename] + '(menu-item "Move to..." dired-do-rename-regexp ; In `dired-aux.el'. + :help "Move marked files matching regexp")) +(define-key diredp-menu-bar-regexp-menu [flag] + '(menu-item "Flag..." dired-flag-files-regexp :help "Flag files matching regexp for deletion")) +(define-key diredp-menu-bar-regexp-menu [image-dired-mark-tagged-files] + '(menu-item "Mark Image Files Tagged..." image-dired-mark-tagged-files + :enable (fboundp 'image-dired-mark-tagged-files) + :help "Mark image files whose image tags match regexp")) +(define-key diredp-menu-bar-regexp-menu [mark-cont] + '(menu-item "Mark Containing..." dired-mark-files-containing-regexp + :help "Mark files whose contents matches regexp")) +(define-key diredp-menu-bar-regexp-menu [mark] + '(menu-item "Mark..." dired-mark-files-regexp + :help "Mark files matching regexp")) + + +;; `Regexp' > `Here and Below' menu. +;; +(defvar diredp-regexp-recursive-menu (make-sparse-keymap "Here and Below")) +(define-key diredp-menu-bar-regexp-menu [mark-recursive] + (cons "Here and Below" diredp-regexp-recursive-menu)) +(define-key diredp-regexp-recursive-menu [diredp-mark-files-regexp-recursive] + '(menu-item "Mark Named..." diredp-mark-files-regexp-recursive + :help "Mark all file names matching a regexp, including those in marked subdirs")) +(define-key diredp-regexp-recursive-menu [diredp-mark-files-containing-regexp-recursive] + '(menu-item "Mark Containing..." diredp-mark-files-containing-regexp-recursive + :help "Mark all files with content matching a regexp, including in marked subdirs")) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-regexp-recursive-menu 'diredp-regexp-recursive-menu)) +(make-obsolete-variable 'diredp-menu-bar-regexp-recursive-menu 'diredp-regexp-recursive-menu) ; 2017-04-09 + +(when (boundp 'diredp-menu-bar-regexp-recursive-menu) + (defalias 'diredp-menu-bar-regexp-recursive-menu diredp-menu-bar-regexp-recursive-menu)) +(make-obsolete 'diredp-menu-bar-regexp-recursive-menu 'diredp-regexp-recursive-menu) ; 2017-04-09 + + +;; "Marks" menu. +;; +;; REPLACE ORIGINAL `Marks' menu in `dired.el'. +;; +(defvar diredp-menu-bar-marks-menu (make-sparse-keymap "Marks")) +(define-key dired-mode-map [menu-bar mark] (cons "Marks" diredp-menu-bar-marks-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-mark-menu 'diredp-menu-bar-marks-menu)) +(make-obsolete-variable 'diredp-menu-bar-mark-menu 'diredp-menu-bar-marks-menu) ; 2017-04-09 + +(define-key diredp-menu-bar-marks-menu [prev] + '(menu-item "Previous Marked" dired-prev-marked-file :help "Move to previous marked file")) +(define-key diredp-menu-bar-marks-menu [next] + '(menu-item "Next Marked" dired-next-marked-file :help "Move to next marked file")) +(define-key diredp-menu-bar-marks-menu [marks] + '(menu-item "Change Mark..." dired-change-marks + :help "Replace a given mark character with another")) +(define-key diredp-menu-bar-marks-menu [toggle-marks] + (if (> emacs-major-version 21) + '(menu-item "Toggle Marked/Unmarked" dired-toggle-marks + :help "Mark unmarked files, unmark marked ones") + '(menu-item "Toggle Marked/Unmarked" dired-toggle-marks + :help "Mark unmarked files, unmark marked ones"))) + + +;; `Marks' > `Tagged' menu. +;; +(when (require 'bookmark+ nil t) + (defvar diredp-marks-tags-menu (make-sparse-keymap "Tagged (Autofiles)") + "`Tags' submenu for Dired menu-bar `Marks' menu.") + (define-key diredp-menu-bar-marks-menu [mark-tags] (cons "Tagged" diredp-marks-tags-menu)) + + (define-key diredp-marks-tags-menu [diredp-unmark-files-tagged-none] + '(menu-item "Unmark Not Tagged with Any..." diredp-unmark-files-tagged-none + :help "Unmark files that are not tagged with *any* of the tags you enter")) + (define-key diredp-marks-tags-menu [diredp-unmark-files-tagged-not-all] + '(menu-item "Unmark Not Tagged with All..." diredp-unmark-files-tagged-not-all + :help "Unmark files that are not tagged with *all* tags")) + (define-key diredp-marks-tags-menu [diredp-unmark-files-tagged-some] + '(menu-item "Unmark Tagged with Some..." diredp-unmark-files-tagged-some + :help "Unmark files that are tagged with at least one of the tags you enter")) + (define-key diredp-marks-tags-menu [diredp-unmark-files-tagged-all] + '(menu-item "Unmark Tagged with All..." diredp-unmark-files-tagged-all + :help "Unmark files that are tagged with *each* tag you enter")) + (define-key diredp-marks-tags-menu [diredp-unmark-files-tagged-regexp] + '(menu-item "Unmark Tagged Matching Regexp..." diredp-unmark-files-tagged-regexp + :help "Unmark files that have at least one tag that matches a regexp")) + (define-key diredp-marks-tags-menu [separator-marks-tags] '("--")) ; ------------------------- + + (define-key diredp-marks-tags-menu [diredp-mark-files-tagged-none] + '(menu-item "Mark Not Tagged with Any..." diredp-mark-files-tagged-none + :help "Mark files that are not tagged with *any* of the tags you enter")) + (define-key diredp-marks-tags-menu [diredp-mark-files-tagged-not-all] + '(menu-item "Mark Not Tagged with All..." diredp-mark-files-tagged-not-all + :help "Mark files that are not tagged with *all* tags")) + (define-key diredp-marks-tags-menu [diredp-mark-files-tagged-some] + '(menu-item "Mark Tagged with Some..." diredp-mark-files-tagged-some + :help "Mark files that are tagged with at least one of the tags you enter")) + (define-key diredp-marks-tags-menu [diredp-mark-files-tagged-all] + '(menu-item "Mark Tagged with All..." diredp-mark-files-tagged-all + :help "Mark files that are tagged with *each* tag you enter")) + (define-key diredp-marks-tags-menu [diredp-mark-files-tagged-regexp] + '(menu-item "Mark Tagged Matching Regexp..." diredp-mark-files-tagged-regexp + :help "Mark files that have at least one tag that matches a regexp"))) + + +;; `Marks' > `Omit' menu. +;; +(defvar diredp-marks-omit-menu (make-sparse-keymap "Omit") + "`Omit' submenu for Dired menu-bar `Marks' menu.") +(define-key diredp-menu-bar-marks-menu [marks-omit] (cons "Omit" diredp-marks-omit-menu)) + +(define-key diredp-marks-omit-menu [marks-omit-unmarked] + '(menu-item "Omit Unmarked" diredp-omit-unmarked :help "Hide lines of unmarked files")) +(define-key diredp-marks-omit-menu [marks-omit-marked] + '(menu-item "Omit Marked" diredp-omit-marked :help "Hide lines of marked files")) + + +;; `Marks' > `Flag' menu. +;; +(defvar diredp-marks-flag-menu (make-sparse-keymap "Flag") + "`Flag' submenu for Dired menu-bar `Marks' menu.") +(define-key diredp-menu-bar-marks-menu [mark-flag] (cons "Flag" diredp-marks-flag-menu)) + +(define-key diredp-marks-flag-menu [marks-flag-extension] + '(menu-item "Flag Extension..." dired-flag-extension ; In `dired-x.el' + :help "Flag all files that have a certain extension, for deletion")) +(define-key diredp-marks-flag-menu [marks-flag-garbage-files] + '(menu-item "Flag Garbage Files" dired-flag-garbage-files + :help "Flag unneeded files for deletion")) +(define-key diredp-marks-flag-menu [marks-flag-backup-files] + '(menu-item "Flag Backup Files" dired-flag-backup-files + :help "Flag all backup files for deletion")) +(define-key diredp-marks-flag-menu [marks-flag-auto-save-files] + '(menu-item "Flag Auto-save Files" dired-flag-auto-save-files + :help "Flag auto-save files for deletion")) +(define-key diredp-marks-flag-menu [marks-flag-region] + '(menu-item "Flag Region" diredp-flag-region-files-for-deletion + :visible (diredp-nonempty-region-p) + :help "Flag all files in the region (selection) for deletion")) +(when (< emacs-major-version 21) + (put 'diredp-flag-region-files-for-deletion 'menu-enable '(diredp-nonempty-region-p))) +(define-key diredp-marks-flag-menu [marks-flag-deletion] + '(menu-item "Flag This" dired-flag-file-deletion + :visible (not (diredp-nonempty-region-p)) + :help "Flag current line's file for deletion")) + + +;; `Marks' > `Unmark' menu. +;; +(defvar diredp-marks-unmark-menu (make-sparse-keymap "Unmark") + "`Unmark' submenu for Dired menu-bar `Marks' menu.") +(define-key diredp-menu-bar-marks-menu [mark-mark] (cons "Unmark" diredp-marks-unmark-menu)) + +(define-key diredp-marks-unmark-menu [unmark-autofiles] + '(menu-item "Unmark Autofiles" diredp-unmark-autofiles + :help "Unmark all autofiles (bookmarks with same name as file)" + :enable (featurep 'bookmark+))) +(define-key diredp-marks-unmark-menu [unmark-all] + '(menu-item "Unmark All" dired-unmark-all-marks :help "Remove all marks from all files")) +(define-key diredp-marks-unmark-menu [unmark-with] + '(menu-item "Unmark Marked-With..." dired-unmark-all-files + :help "Remove a specific mark (or all marks) from every file")) +(define-key diredp-marks-unmark-menu [unmark-region] + '(menu-item "Unmark Region" diredp-unmark-region-files + :visible (diredp-nonempty-region-p) + :help "Unmark all files in the region (selection)")) +(when (< emacs-major-version 21) + (put 'diredp-unmark-region-files 'menu-enable '(diredp-nonempty-region-p))) +(define-key diredp-marks-unmark-menu [unmark-this] + '(menu-item "Unmark This" dired-unmark + :visible (not (diredp-nonempty-region-p)) + :help "Unmark or unflag current line's file")) + + +;; `Marks' > `Mark' menu. +;; +(defvar diredp-marks-mark-menu (make-sparse-keymap "Mark") + "`Mark' submenu for Dired menu-bar `Marks' menu.") +(define-key diredp-menu-bar-marks-menu [marks-mark] (cons "Mark" diredp-marks-mark-menu)) + +(define-key diredp-marks-mark-menu [marks-mark-sexp] + '(menu-item "Mark If..." dired-mark-sexp ; In `dired-x.el'. + :help "Mark files that satisfy specified condition")) +(define-key diredp-marks-mark-menu [marks-image-dired-mark-tagged-files] + '(menu-item "Mark Image Files Tagged..." image-dired-mark-tagged-files + :enable (fboundp 'image-dired-mark-tagged-files) ; In `image-dired.el'. + :help "Mark image files whose image tags match regexp")) +(define-key diredp-marks-mark-menu [marks-mark-cont] + '(menu-item "Mark Content Matching Regexp..." dired-mark-files-containing-regexp + :help "Mark files whose contents matches regexp")) +(define-key diredp-marks-mark-menu [marks-mark...] + '(menu-item "Mark Name Matching Regexp..." dired-mark-files-regexp + :help "Mark file names matching regexp")) +(when (fboundp 'dired-mark-omitted) ; In `dired-x.el', Emacs 22+. + (define-key diredp-marks-mark-menu [marks-mark-omitted] + '(menu-item "Mark Omitted..." dired-mark-omitted + :help "Mark all omitted files and subdirectories"))) +(define-key diredp-marks-mark-menu [marks-mark-extension] + '(menu-item "Mark Extension..." diredp-mark/unmark-extension + :help "Mark all files with specified extension")) +(define-key diredp-marks-mark-menu [marks-mark-autofiles] + '(menu-item "Mark Autofiles" diredp-mark-autofiles + :help "Mark all autofiles (bookmarks with same name as file)" + :enable (featurep 'bookmark+))) +(define-key diredp-marks-mark-menu [marks-mark-symlinks] + '(menu-item "Mark Symlinks" dired-mark-symlinks + :visible (fboundp 'make-symbolic-link) :help "Mark all symbolic links")) +(define-key diredp-marks-mark-menu [marks-mark-directories] + '(menu-item "Mark Directories" dired-mark-directories + :help "Mark all directories except `.' and `..'")) +(define-key diredp-marks-mark-menu [marks-mark-directory] + '(menu-item "Mark Old Backups" dired-clean-directory + :help "Flag old numbered backups for deletion")) +(define-key diredp-marks-mark-menu [marks-mark-executables] + '(menu-item "Mark Executables" dired-mark-executables :help "Mark all executable files")) +(define-key diredp-marks-mark-menu [marks-mark-region] + '(menu-item "Mark Region" diredp-mark-region-files + :visible (diredp-nonempty-region-p) + :help "Mark all of the files in the region (selection)")) +(when (< emacs-major-version 21) + (put 'diredp-mark-region-files 'menu-enable '(diredp-nonempty-region-p))) +(define-key diredp-marks-mark-menu [marks-mark-this] + '(menu-item "Mark This" dired-mark + :visible (not (diredp-nonempty-region-p)) + :help "Mark current line's file for future operations")) + + +;; `Marks' > `Here and Below' menu. +;; +(defvar diredp-marks-recursive-menu (make-sparse-keymap "Here and Below")) +(define-key diredp-menu-bar-marks-menu [mark-recursive] + (cons "Here and Below" diredp-marks-recursive-menu)) + +(define-key diredp-marks-recursive-menu [diredp-flag-auto-save-files-recursive] + '(menu-item "Flag Auto-Save Files..." diredp-flag-auto-save-files-recursive + :help "Flag all auto-save files for deletion, including those in marked subdirs")) +(when (fboundp 'diredp-unmark-all-marks-recursive) ; Emacs 22+ + (define-key diredp-marks-recursive-menu [diredp-change-marks-recursive] + '(menu-item "Change Mark..." diredp-change-marks-recursive + :help "Change all OLD marks to NEW marks, including those in marked subdirs")) + (define-key diredp-marks-recursive-menu [diredp-unmark-all-files-recursive] + '(menu-item "Unmark Marked-With..." diredp-unmark-all-files-recursive + :help "Remove a given mark everywhere, including in marked subdirs")) + (define-key diredp-marks-recursive-menu [diredp-unmark-all-marks-recursive] + '(menu-item "Unmark All..." diredp-unmark-all-marks-recursive + :help "Remove ALL marks everywhere, including in marked subdirs")) + (define-key diredp-marks-recursive-menu [separator-1] '("--"))) ; ------------ +(define-key diredp-marks-recursive-menu [diredp-mark-sexp-recursive] + '(menu-item "If..." diredp-mark-sexp-recursive + :help "Mark files satisfying specified condition, including those in marked subdirs")) +(define-key diredp-marks-recursive-menu [diredp-mark-files-containing-regexp-recursive] + '(menu-item "Containing Regexp..." diredp-mark-files-containing-regexp-recursive + :help "Mark all files with content matching a regexp, including in marked subdirs")) +(define-key diredp-marks-recursive-menu [diredp-mark-files-regexp-recursive] + '(menu-item "Named Regexp..." diredp-mark-files-regexp-recursive + :help "Mark all file names matching a regexp, including those in marked subdirs")) +(define-key diredp-marks-recursive-menu [diredp-mark-extension-recursive] + '(menu-item "Extension..." diredp-mark-extension-recursive + :help "Mark all files with a given extension, including those in marked subdirs")) +(define-key diredp-marks-recursive-menu [diredp-mark-autofiles-recursive] + '(menu-item "Autofiles" diredp-mark-autofiles-recursive + :help "Mark all files with a given extension, including those in marked subdirs" + :enable (featurep 'bookmark+))) +(define-key diredp-marks-recursive-menu [diredp-mark-symlinks-recursive] + '(menu-item "Symbolic Links" diredp-mark-symlinks-recursive + :help "Mark all symbolic links, including those in marked subdirs")) +(define-key diredp-marks-recursive-menu [diredp-mark-directories-recursive] + '(menu-item "Directories" diredp-mark-directories-recursive + :help "Mark all directories, including those in marked subdirs")) +(define-key diredp-marks-recursive-menu [diredp-mark-executables-recursive] + '(menu-item "Executables" diredp-mark-executables-recursive + :help "Mark all executable files, including those in marked subdirs")) + + +;; "Dir" menu. +;; +;; REPLACE ORIGINAL `Subdir' menu in `dired.el'. +;; +(defvar diredp-menu-bar-dir-menu (make-sparse-keymap "Dir")) +(define-key dired-mode-map [menu-bar subdir] (cons "Dir" diredp-menu-bar-dir-menu)) + +;; We don't use `define-obsolete-variable-alias' so that byte-compilation in older Emacs +;; works for newer Emacs too. +(when (fboundp 'defvaralias) ; Emacs 22+ + (defvaralias 'diredp-menu-bar-subdir-menu 'diredp-dir-menu)) +(make-obsolete-variable 'diredp-menu-bar-subdir-menu 'diredp-dir-menu) ; 2017-04-09 + +(when (boundp 'diredp-menu-bar-subdir-menu) + (defalias 'diredp-menu-bar-subdir-menu diredp-menu-bar-subdir-menu)) +(make-obsolete 'diredp-menu-bar-subdir-menu 'diredp-dir-menu) ; 2017-04-09 + + +;; `Dir' > `Hide/Show' menu. +;; +(defvar diredp-hide/show-menu (make-sparse-keymap "Hide/Show") + "`Hide/Show' submenu for Dired menu-bar `Dir' menu.") +(define-key diredp-menu-bar-dir-menu [hide-show] (cons "Hide/Show" diredp-hide/show-menu)) + +(when (fboundp 'dired-omit-mode) + (define-key diredp-hide/show-menu [dired-omit-mode] + '(menu-item "Hide/Show Uninteresting (Omit Mode)" dired-omit-mode + :help "Toggle omission of uninteresting files (Omit mode)"))) +(when (fboundp 'dired-hide-details-mode) ; Emacs 24.4+ + (define-key diredp-hide/show-menu [hide-details] + '(menu-item "Hide/Show Details" dired-hide-details-mode + :help "Hide or show less important fields of directory listing"))) +(define-key diredp-hide/show-menu [hide-all] + '(menu-item "Hide/Show All Subdirs" dired-hide-all + :help "Hide all subdirectories, leave only header lines")) +(define-key diredp-hide/show-menu [hide-subdir] + '(menu-item "Hide/Show Subdir" diredp-hide-subdir-nomove + :help "Hide or unhide current directory listing")) + + +;; `Dir' > `Bookmark' menu. +;; +(defvar diredp-bookmark-menu (make-sparse-keymap "Bookmark") + "`Bookmark' submenu for Dired menu-bar `Dir' menu.") +(define-key diredp-menu-bar-dir-menu [bookmark] (cons "Bookmark" diredp-bookmark-menu)) + +(define-key diredp-bookmark-menu [diredp-highlight-autofiles-mode] + '(menu-item "Toggle Autofile Highlighting" diredp-highlight-autofiles-mode + :help "Toggle whether to highlight autofile bookmarks" + :visible (and (featurep 'bookmark+) (featurep 'highlight)))) +(define-key diredp-bookmark-menu [diredp-do-bookmark-dirs-recursive] + '(menu-item "Bookmark Dirs Here and Below..." diredp-do-bookmark-dirs-recursive + :help "Bookmark this Dired buffer and marked subdirectory Dired buffers, recursively.")) +(define-key diredp-bookmark-menu [bookmark-dired] + '(menu-item "Bookmark Dired Buffer..." bookmark-set :help "Bookmark this Dired buffer")) + + +;; `Dir' > `Navigate' menu. +;; +(defvar diredp-navigate-menu (make-sparse-keymap "Navigate") + "`Navigate' submenu for Dired menu-bar `Dir' menu.") +(define-key diredp-menu-bar-dir-menu [navigate] (cons "Navigate" diredp-navigate-menu)) + +(define-key diredp-navigate-menu [insert] + '(menu-item "Move To This Subdir" dired-maybe-insert-subdir + :help "Move to subdirectory line or listing")) +(define-key diredp-navigate-menu [tree-down] + '(menu-item "Tree Down" dired-tree-down :help "Go to first subdirectory header down the tree")) +(define-key diredp-navigate-menu [tree-up] + '(menu-item "Tree Up" dired-tree-up :help "Go to first subdirectory header up the tree")) +(define-key diredp-navigate-menu [up] + '(menu-item "Up Directory" diredp-up-directory :help "Dired the parent directory")) +(define-key diredp-navigate-menu [prev-subdir] + '(menu-item "Prev Subdir" diredp-prev-subdir :help "Go to previous subdirectory header line")) +(define-key diredp-navigate-menu [next-subdir] + '(menu-item "Next Subdir" diredp-next-subdir :help "Go to next subdirectory header line")) +(define-key diredp-navigate-menu [prev-dirline] + '(menu-item "Prev Dirline" diredp-prev-dirline :help "Move to previous directory-file line")) +(define-key diredp-navigate-menu [next-dirline] + '(menu-item "Next Dirline" diredp-next-dirline :help "Move to next directory-file line")) + +(define-key diredp-menu-bar-dir-menu [separator-subdir] '("--")) ; -------------------------- + +(define-key diredp-menu-bar-dir-menu [image-dired-dired-toggle-marked-thumbs] + '(menu-item "Toggle Image Thumbnails" image-dired-dired-toggle-marked-thumbs + :enable (fboundp 'image-dired-dired-toggle-marked-thumbs) + :help "Add or remove image thumbnails in front of marked file names")) +(when (fboundp 'dired-isearch-filenames) ; Emacs 23+ + (define-key diredp-menu-bar-dir-menu [isearch-filenames-regexp] + '(menu-item "Isearch Regexp in File Names..." dired-isearch-filenames-regexp + :help "Incrementally search for regexp in file names only")) + (define-key diredp-menu-bar-dir-menu [isearch-filenames] + '(menu-item "Isearch in File Names..." dired-isearch-filenames + :help "Incrementally search for literal text in file names only."))) +(when (or (> emacs-major-version 21) (fboundp 'wdired-change-to-wdired-mode)) + (define-key diredp-menu-bar-dir-menu [wdired-mode] + '(menu-item "Edit File Names (WDired)" wdired-change-to-wdired-mode + :help "Put a Dired buffer in a mode in which filenames are editable" + :keys "C-x C-q" :filter (lambda (x) (and (derived-mode-p 'dired-mode) x))))) +(define-key diredp-menu-bar-dir-menu [diredp-yank-files] + '(menu-item "Paste Files from Copied Absolute Names" diredp-yank-files + :help "Paste files here whose absolute names you copied" + :enable (catch 'dir-menu--yank-files + (let ((files (car kill-ring-yank-pointer))) + (and (stringp files) + (dolist (file (split-string files)) + (unless (file-name-absolute-p file) (throw 'dir-menu--yank-files nil))))) + t))) +(when (fboundp 'dired-compare-directories) ; Emacs 22+ + (define-key diredp-menu-bar-dir-menu [compare-directories] + '(menu-item "Compare Directories..." dired-compare-directories + :help "Mark files with different attributes in two Dired buffers"))) + +(define-key diredp-menu-bar-dir-menu [separator-dired-on-set] '("--")) ; -------------------- + +(define-key diredp-menu-bar-dir-menu [diredp-dired-recent-dirs] + '(menu-item "Dired Recent Directories..." diredp-dired-recent-dirs + :visible (boundp 'recentf-list) :enable (and (boundp 'recentf-list) (consp recentf-list)) + :help "Open a Dired buffer for recently used directories")) +(define-key diredp-menu-bar-dir-menu [diredp-dired-inserted-subdirs] + '(menu-item "Dired Each Inserted Subdir..." diredp-dired-inserted-subdirs + :enable (cdr dired-subdir-alist) ; First elt is current dir. Must have at least one more. + :help "Open Dired separately for each of the inserted subdirectories")) +(define-key diredp-menu-bar-dir-menu [diredp-add-to-this-dired-buffer] + '(menu-item "Add Entries Here..." diredp-add-to-this-dired-buffer + :help "Add individual file and directory names to the listing" + :keys "C-x E")) +(define-key diredp-menu-bar-dir-menu [diredp-dired-union] + '(menu-item "Dired Union..." diredp-dired-union + :help "Open Dired for the union of some existing Dired buffers")) +(define-key diredp-menu-bar-dir-menu [diredp-fileset-other-window] + '(menu-item "Dired Fileset..." diredp-fileset-other-window + :enable (> emacs-major-version 21) :help "Open Dired on an Emacs fileset")) +(define-key diredp-menu-bar-dir-menu [diredp-dired-for-files] + '(menu-item "Dired Files Located Anywhere" diredp-dired-for-files + :help "Open Dired on specific files whose names you provide")) +(define-key diredp-menu-bar-dir-menu [diredp-marked-other-window] + '(menu-item "Dired Marked Files in Other Window" diredp-marked-other-window + :enable (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t))) + :help "Open Dired on marked files only, in other window")) +(define-key diredp-menu-bar-dir-menu [diredp-marked] + '(menu-item "Dired Marked Files" diredp-marked + :enable (save-excursion (goto-char (point-min)) + (and (re-search-forward (dired-marker-regexp) nil t) + (re-search-forward (dired-marker-regexp) nil t))) + :help "Open Dired on marked files only")) +(define-key diredp-menu-bar-dir-menu [dired] + '(menu-item "Dired (Filter via Wildcards)..." dired + :help "Explore a directory (you can provide wildcards)")) + +(define-key diredp-menu-bar-dir-menu [separator-dired] '("--")) ; --------------------- + +(define-key diredp-menu-bar-dir-menu [insert] + '(menu-item "Insert/Move-To This Subdir" dired-maybe-insert-subdir + :help "Move to subdirectory line or listing")) +(define-key diredp-menu-bar-dir-menu [revert] + '(menu-item "Refresh (Sync \& Show All)" revert-buffer :help "Update directory contents")) +(define-key diredp-menu-bar-dir-menu [create-directory] ; Moved from "Immediate". + '(menu-item "New Directory..." dired-create-directory :help "Create a directory")) + + +;;; Mouse-3 menu binding. +(define-key dired-mode-map [down-mouse-3] 'diredp-mouse-3-menu) +(define-key dired-mode-map [mouse-3] 'ignore) + + +;;; Non-menu Dired bindings. + +;; Move `dired-omit-mode' to `C-x M-o', so prefix key `M-o' is free for face/font-lock stuff. +(define-key dired-mode-map "\C-x\M-o" (if (fboundp 'dired-omit-mode) 'dired-omit-mode 'dired-omit-toggle)) +(when (memq (lookup-key dired-mode-map "\M-o") '(dired-omit-mode dired-omit-toggle)) + (define-key dired-mode-map "\M-o" nil)) + +;; These are global, not just Dired mode. They are on prefix key `C-x D'. +(unless (lookup-key ctl-x-map "D") + (define-key ctl-x-map "D" nil) ; For Emacs 20 + (define-key ctl-x-map "DA" 'diredp-add-to-dired-buffer) ; `C-x D A' + (define-key ctl-x-map "DF" 'diredp-dired-for-files) ; `C-x D F' + (define-key ctl-x-map "DR" 'diredp-dired-recent-dirs) ; `C-x D R' + (define-key ctl-x-map "DS" 'diredp-fileset) ; `C-x D S' + (define-key ctl-x-map "DU" 'diredp-dired-union)) ; `C-x D U' + +(unless (lookup-key ctl-x-4-map "D") + (define-key ctl-x-4-map "D" nil) ; For Emacs 20 + (define-key ctl-x-4-map "DA" 'diredp-add-to-dired-buffer-other-window) ; `C-x 4 D A' + (define-key ctl-x-4-map "DF" 'diredp-dired-for-files-other-window) ; `C-x 4 D F' + (define-key ctl-x-4-map "DR" 'diredp-dired-recent-dirs-other-window) ; `C-x 4 D R' + (define-key ctl-x-4-map "DS" 'diredp-fileset-other-window) ; `C-x 4 D S' + (define-key ctl-x-4-map "DU" 'diredp-dired-union-other-window)) ; `C-x 4 D U' + +;; Navigation +(substitute-key-definition 'dired-up-directory 'diredp-up-directory dired-mode-map) +(substitute-key-definition 'dired-next-line 'diredp-next-line dired-mode-map) +(substitute-key-definition 'dired-previous-line 'diredp-previous-line dired-mode-map) +(substitute-key-definition 'dired-next-dirline 'diredp-next-dirline dired-mode-map) +(substitute-key-definition 'dired-prev-dirline 'diredp-prev-dirline dired-mode-map) +(substitute-key-definition 'dired-next-subdir 'diredp-next-subdir dired-mode-map) +(substitute-key-definition 'dired-prev-subdir 'diredp-prev-subdir dired-mode-map) + + +(define-key dired-mode-map [S-down-mouse-1] 'ignore) ; (normally `mouse-set-font') +;; `diredp-mouse-mark-region-files' provides Windows-Explorer behavior +;; for selecting (marking) files. +(define-key dired-mode-map [S-mouse-1] 'diredp-mouse-mark-region-files) ; `S-mouse-1' +(define-key dired-mode-map [mouse-2] 'dired-mouse-find-file-other-window) ; `mouse-2' +;; But be aware that `dired-sort-menu.el' binds `S-mouse-2' to `dired-sort-menu-popup'. +(define-key dired-mode-map [S-down-mouse-2] 'dired-mouse-find-file) ; `S-mouse-2' +(define-key dired-mode-map [S-mouse-2] 'ignore) +(define-key dired-mode-map [M-mouse-2] 'diredp-mouse-find-file-other-frame) ; `M-mouse-2' + +;; On Windows, bind more. +(eval-after-load "w32-browser" + '(progn + (define-key dired-mode-map [(control return)] 'dired-w32explore) ; `C-RET' + (define-key dired-mode-map [(meta return)] 'dired-w32-browser) ; `M-RET' + (define-key dired-mode-map [mouse-2] 'dired-mouse-w32-browser) ; `mouse-2' + (define-key dired-mode-map (kbd "") 'dired-multiple-w32-browser))) ; `C-M-RET' + +(when (fboundp 'diredp-w32-drives) + (when (< emacs-major-version 21) (define-key dired-mode-map ":" nil)) ; For Emacs 20 + (define-key dired-mode-map ":/" 'diredp-w32-drives)) ; `:/' + +;; Other keyboard keys +(define-key dired-mode-map "@" 'diredp-do-apply-function) ; `@' +(define-key dired-mode-map "\$" 'diredp-hide-subdir-nomove) ; `$' +(define-key dired-mode-map "\M-$" 'dired-hide-subdir) ; `M-$' +(define-key dired-mode-map "=" 'diredp-ediff) ; `=' +;; This replaces the `dired-x.el' binding of `dired-mark-extension'. +(define-key dired-mode-map "*." 'diredp-mark/unmark-extension) ; `* .' +(define-key dired-mode-map "*B" 'diredp-mark-autofiles) ; `* B' +(define-key dired-mode-map [(control meta ?*)] 'diredp-marked-other-window) ; `C-M-*' +(define-key dired-mode-map "\M-a" 'dired-do-search) ; `M-a' +(define-key dired-mode-map "\M-b" 'diredp-do-bookmark) ; `M-b' +(define-key dired-mode-map "\C-\M-b" 'diredp-set-bookmark-file-bookmark-for-marked) ; `C-M-b' +(when diredp-bind-problematic-terminal-keys + (define-key dired-mode-map [(control meta shift ?b)] ; `C-M-B' (aka `C-M-S-b') + 'diredp-do-bookmark-in-bookmark-file)) +(define-key dired-mode-map "e" 'diredp-visit-this-file) ; `e' (was `dired-find-file') +(define-key dired-mode-map [C-down] 'diredp-visit-next-file) ; `C-down' (was `forward-paragraph') +(define-key dired-mode-map [C-up] 'diredp-visit-previous-file) ; `C-up' (was `backward-paragraph') +(define-key dired-mode-map "\C-\M-G" 'diredp-do-grep) ; `C-M-G' +(when (fboundp 'mkhtml-dired-files) ; In `mkhtml.el'. + (define-key dired-mode-map "\M-h" 'mkhtml-dired-files)) ; `M-h' +(define-key dired-mode-map "\C-\M-i" 'diredp-dired-inserted-subdirs) ; `C-M-i' +(define-key dired-mode-map "\M-q" (if (< emacs-major-version 21) + 'dired-do-query-replace + 'dired-do-query-replace-regexp)) ; `M-q' +(when diredp-bind-problematic-terminal-keys + (define-key dired-mode-map [(control meta shift ?r)] ; `C-M-R' (aka `C-M-S-r') + 'diredp-toggle-find-file-reuse-dir)) +(define-key dired-mode-map "U" 'dired-unmark-all-marks) ; `U' +(substitute-key-definition 'describe-mode 'diredp-describe-mode ; `h', `C-h m' + dired-mode-map (current-global-map)) + +;; Tags - same keys as in `*Bookmark List*'. +;; +;; NOTE: If this changes then need to update `dired-sort-menu+.el' to reflect the changes. +;; +(define-key dired-mode-map "T" nil) ; For Emacs 20 +(define-key dired-mode-map "T+" 'diredp-tag-this-file) ; `T +' +(define-key dired-mode-map "T-" 'diredp-untag-this-file) ; `T -' +(define-key dired-mode-map "T0" 'diredp-remove-all-tags-this-file) ; `T 0' +(define-key dired-mode-map "Tc" 'diredp-copy-tags-this-file) ; `T c' +(define-key dired-mode-map "Tp" 'diredp-paste-add-tags-this-file) ; `T p' +(define-key dired-mode-map "Tq" 'diredp-paste-replace-tags-this-file) ; `T q' +(define-key dired-mode-map "Tv" 'diredp-set-tag-value-this-file) ; `T v' +(define-key dired-mode-map "T\M-w" 'diredp-copy-tags-this-file) ; `T M-w' +(define-key dired-mode-map "T\C-y" 'diredp-paste-add-tags-this-file) ; `T C-y' +(define-key dired-mode-map "T>+" 'diredp-do-tag) ; `T > +' +(define-key dired-mode-map "T>-" 'diredp-do-untag) ; `T > -' +(define-key dired-mode-map "T>0" 'diredp-do-remove-all-tags) ; `T > 0' +(define-key dired-mode-map "T>p" 'diredp-do-paste-add-tags) ; `T > p' +(define-key dired-mode-map "T>q" 'diredp-do-paste-replace-tags) ; `T > q' +(define-key dired-mode-map "T>v" 'diredp-do-set-tag-value) ; `T > v' +(define-key dired-mode-map "T>\C-y" 'diredp-do-paste-add-tags) ; `T > C-y' +(define-key dired-mode-map "Tm%" 'diredp-mark-files-tagged-regexp) ; `T m %' +(define-key dired-mode-map "Tm*" 'diredp-mark-files-tagged-all) ; `T m *' +(define-key dired-mode-map "Tm+" 'diredp-mark-files-tagged-some) ; `T m +' +(define-key dired-mode-map "Tm~*" 'diredp-mark-files-tagged-not-all) ; `T m ~ *' +(define-key dired-mode-map "Tm~+" 'diredp-mark-files-tagged-none) ; `T m ~ +' +(define-key dired-mode-map "Tu%" 'diredp-unmark-files-tagged-regexp) ; `T u %' +(define-key dired-mode-map "Tu*" 'diredp-unmark-files-tagged-all) ; `T u *' +(define-key dired-mode-map "Tu+" 'diredp-unmark-files-tagged-some) ; `T u +' +(define-key dired-mode-map "Tu~*" 'diredp-unmark-files-tagged-not-all) ; `T u ~ *' +(define-key dired-mode-map "Tu~+" 'diredp-unmark-files-tagged-none) ; `T u ~ +' +;; $$$$$$ (define-key dired-mode-map [(control ?+)] 'diredp-do-tag) +;; $$$$$$ (define-key dired-mode-map [(control ?-)] 'diredp-do-untag) + + +;; Vanilla Emacs binds `c' to `dired-do-compress-to'. Use `M-z' instead'. +;; (`dired-sort-menu.el' binds `c' to `dired-sort-menu-toggle-ignore-case'.) +;; +(when (fboundp 'dired-do-compress-to) ; Emacs 25+ + (define-key dired-mode-map (kbd "M-z") 'dired-do-compress-to)) + + +;; Commands for operating on the current line's file. When possible, +;; these are lower-case versions of the upper-case commands for operating on +;; the marked files. (Most of the other corresponding lower-case letters are already +;; defined and cannot be used here.) + +;; $$$$$$ (define-key dired-mode-map [(control meta ?+)] 'diredp-tag-this-file) +;; $$$$$$ (define-key dired-mode-map [(control meta ?-)] 'diredp-untag-this-file) +(define-key dired-mode-map "\r" 'dired-find-file) ; `RET' +(when (fboundp 'diredp-describe-file) + (define-key dired-mode-map (kbd "C-h RET") 'diredp-describe-file) ; `C-h RET' + (define-key dired-mode-map (kbd "C-h C-") 'diredp-describe-file)) ; `C-h C-RET' +(define-key dired-mode-map "%c" 'diredp-capitalize) ; `% c' +(define-key dired-mode-map "b" 'diredp-byte-compile-this-file) ; `b' +(define-key dired-mode-map [(control shift ?b)] 'diredp-bookmark-this-file) ; `C-B' +(define-key dired-mode-map "\M-c" 'diredp-capitalize-this-file) ; `M-c' +(when (and (fboundp 'diredp-chgrp-this-file) diredp-bind-problematic-terminal-keys) + (define-key dired-mode-map [(control meta shift ?g)] 'diredp-chgrp-this-file)) ; `C-M-G' (aka `C-M-S-g') +(define-key dired-mode-map "\M-i" 'diredp-insert-subdirs) ; `M-i' +(define-key dired-mode-map "\M-l" 'diredp-downcase-this-file) ; `M-l' +(define-key dired-mode-map "\C-\M-l" 'diredp-list-marked) ; `C-M-l' +(when diredp-bind-problematic-terminal-keys + (define-key dired-mode-map [(meta shift ?m)] 'diredp-chmod-this-file)) ; `M-M' (aka `M-S-m') +(define-key dired-mode-map "\C-o" 'diredp-find-file-other-frame) ; `C-o' +(when (and (fboundp 'diredp-chown-this-file) diredp-bind-problematic-terminal-keys) + (define-key dired-mode-map [(meta shift ?o)] 'diredp-chown-this-file)) ; `M-O' (aka `M-S-o') +(define-key dired-mode-map "\C-\M-o" 'dired-display-file) ; `C-M-o' (not `C-o') +(define-key dired-mode-map "\M-p" 'diredp-print-this-file) ; `M-p' +(define-key dired-mode-map "r" 'diredp-rename-this-file) ; `r' +(when (fboundp 'image-dired-dired-display-image) + (define-key dired-mode-map "\C-tI" 'diredp-image-show-this-file)) ; `C-t I' +(when diredp-bind-problematic-terminal-keys + (define-key dired-mode-map [(meta shift ?t)] 'diredp-touch-this-file) ; `M-T' (aka `M-S-t') + (define-key dired-mode-map [(control meta shift ?t)] 'dired-do-touch)) ; `C-M-T' (aka `C-M-S-t') +(define-key dired-mode-map "\M-u" 'diredp-upcase-this-file) ; `M-u' +(define-key dired-mode-map "y" 'diredp-relsymlink-this-file) ; `y' +(define-key dired-mode-map "\C-w" 'diredp-move-files-named-in-kill-ring) ; `C-w' +(define-key dired-mode-map "\C-y" 'diredp-yank-files) ; `C-y' +(define-key dired-mode-map "z" 'diredp-compress-this-file) ; `z' +(when (fboundp 'dired-show-file-type) + (define-key dired-mode-map "_" 'dired-show-file-type)) ; `_' (underscore) +(substitute-key-definition 'kill-line 'diredp-delete-this-file ; `C-k', `delete', `deleteline' + dired-mode-map (current-global-map)) + + +;; Commands that handle marked below, recursively. +;; Use `M-+' as a prefix key for all such commands. + +(define-prefix-command 'diredp-recursive-map) +(define-key dired-mode-map "\M-+" diredp-recursive-map) ; `M-+' + +(when (fboundp 'char-displayable-p) ; Emacs 22+ + (define-key diredp-recursive-map "\M-\C-?" 'diredp-unmark-all-files-recursive)) ; `M-DEL' +(define-key diredp-recursive-map "@" 'diredp-do-apply-function-recursive) ; `@' +(define-key diredp-recursive-map "#" 'diredp-flag-auto-save-files-recursive) ; `#' +(define-key diredp-recursive-map "*@" 'diredp-mark-symlinks-recursive) ; `* @' +(define-key diredp-recursive-map "**" 'diredp-mark-executables-recursive) ; `* *' +(define-key diredp-recursive-map "*/" 'diredp-mark-directories-recursive) ; `* /' +(define-key diredp-recursive-map "*." 'diredp-mark-extension-recursive) ; `* .' +(define-key diredp-recursive-map "*(" 'diredp-mark-sexp-recursive) ; `* (' +(define-key diredp-recursive-map "*B" 'diredp-mark-autofiles-recursive) ; `* B' +(when (fboundp 'char-displayable-p) ; Emacs 22+ + (define-key diredp-recursive-map "*c" 'diredp-change-marks-recursive)) ; `* c' +(define-key diredp-recursive-map "*%" 'diredp-mark-files-regexp-recursive) ; `* %' +(when (> emacs-major-version 22) + (define-key diredp-recursive-map ":d" 'diredp-do-decrypt-recursive) ; `: d' + (define-key diredp-recursive-map ":e" 'diredp-do-encrypt-recursive) ; `: e' + (define-key diredp-recursive-map ":s" 'diredp-do-sign-recursive) ; `: s' + (define-key diredp-recursive-map ":v" 'diredp-do-verify-recursive)) ; `: v' +(define-key diredp-recursive-map "%c" 'diredp-capitalize-recursive) ; `% c' +(define-key diredp-recursive-map "%g" 'diredp-mark-files-containing-regexp-recursive) ; `% g' +(define-key diredp-recursive-map "%l" 'diredp-downcase-recursive) ; `% l' +(define-key diredp-recursive-map "%m" 'diredp-mark-files-regexp-recursive) ; `% m' +(define-key diredp-recursive-map "%u" 'diredp-upcase-recursive) ; `% u' +(when (fboundp 'dired-do-async-shell-command) ; Emacs 23+ + (define-key diredp-recursive-map "&" 'diredp-do-async-shell-command-recursive)) ; `&' +(define-key diredp-recursive-map "!" 'diredp-do-shell-command-recursive) ; `!' +(define-key diredp-recursive-map (kbd "C-M-*") 'diredp-marked-recursive-other-window) ; `C-M-*' +(define-key diredp-recursive-map "A" 'diredp-do-search-recursive) ; `A' +(define-key diredp-recursive-map "\M-b" 'diredp-do-bookmark-recursive) ; `M-b' +(when diredp-bind-problematic-terminal-keys + (define-key diredp-recursive-map [(meta shift ?b)] ; `M-B' (aka `M-S-b') + 'diredp-do-bookmark-dirs-recursive)) +(define-key diredp-recursive-map (kbd "C-M-b") ; `C-M-b' + 'diredp-set-bookmark-file-bookmark-for-marked-recursive) +(when diredp-bind-problematic-terminal-keys + (define-key diredp-recursive-map [(control meta shift ?b)] ; `C-M-B' (aka `C-M-S-b') + 'diredp-do-bookmark-in-bookmark-file-recursive)) +(define-key diredp-recursive-map "C" 'diredp-do-copy-recursive) ; `C' +(define-key diredp-recursive-map "D" 'diredp-do-delete-recursive) ; `D' +(define-key diredp-recursive-map "F" 'diredp-do-find-marked-files-recursive) ; `F' +(when (fboundp 'diredp-do-chgrp-recursive) + (define-key diredp-recursive-map "G" 'diredp-do-chgrp-recursive)) ; `G' +(define-key diredp-recursive-map "\C-\M-G" 'diredp-do-grep-recursive) ; `C-M-G' +(define-key diredp-recursive-map "H" 'diredp-do-hardlink-recursive) ; `H' +(define-key diredp-recursive-map "\M-i" 'diredp-insert-subdirs-recursive) ; `M-i' +(define-key diredp-recursive-map "\C-\M-l" 'diredp-list-marked-recursive) ; `C-M-l' +(define-key diredp-recursive-map "M" 'diredp-do-chmod-recursive) ; `M' +(when (fboundp 'diredp-do-chown-recursive) + (define-key diredp-recursive-map "O" 'diredp-do-chown-recursive)) ; `O' +(define-key diredp-recursive-map "P" 'diredp-do-print-recursive) ; `P' +(define-key diredp-recursive-map "Q" 'diredp-do-query-replace-regexp-recursive) ; `Q' +(define-key diredp-recursive-map "R" 'diredp-do-move-recursive) ; `R' +(define-key diredp-recursive-map "S" 'diredp-do-symlink-recursive) ; `S' +(define-key diredp-recursive-map (kbd "M-s a C-s") ; `M-s a C-s' + 'diredp-do-isearch-recursive) +(define-key diredp-recursive-map (kbd "M-s a C-M-s") ; `M-s a C-M-s' + 'diredp-do-isearch-regexp-recursive) +(when diredp-bind-problematic-terminal-keys + (define-key diredp-recursive-map [(control meta shift ?t)] + 'diredp-do-touch-recursive)) ; `C-M-T' (aka `C-M-S-t') +(define-key diredp-recursive-map "\C-tc" 'diredp-image-dired-comment-files-recursive) ; `C-t c' +(define-key diredp-recursive-map "\C-td" 'diredp-image-dired-display-thumbs-recursive) ; `C-t d' +(define-key diredp-recursive-map "\C-tr" 'diredp-image-dired-delete-tag-recursive) ; `C-t r' +(define-key diredp-recursive-map "\C-tt" 'diredp-image-dired-tag-files-recursive) ; `C-t t' +(when (fboundp 'char-displayable-p) ; Emacs 22+ + (define-key diredp-recursive-map "U" 'diredp-unmark-all-marks-recursive)) ; `U' +(define-key diredp-recursive-map "\M-(" 'diredp-mark-sexp-recursive) ; `M-(' +(define-key diredp-recursive-map "\M-w" 'diredp-copy-filename-as-kill-recursive) ; `M-w' +(define-key diredp-recursive-map "Y" 'diredp-do-relsymlink-recursive) ; `Y' + +(eval-after-load "w32-browser" + '(define-key diredp-recursive-map (kbd "") 'diredp-multiple-w32-browser-recursive)) ; `C-M-RET' + +;; Undefine some bindings that would try to modify a Dired buffer. Their key sequences will +;; then appear to the user as available for local (Dired) definition. +(when (fboundp 'undefine-killer-commands) (undefine-killer-commands dired-mode-map)) + +;;;;;;;;;;;; + +(setq diredp-loaded-p t) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; dired+.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/vendor/reason-indent.el b/users/wpcarro/emacs/.emacs.d/vendor/reason-indent.el new file mode 100644 index 000000000..8fd3c9425 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/vendor/reason-indent.el @@ -0,0 +1,304 @@ +;;; reason-indent.el --- Indentation functions for ReasonML -*-lexical-binding: t-*- + +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;;; Commentary: + +;; Indentation functions for Reason. + +;;; Code: + +(defconst reason-re-ident "[[:word:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*") + +(defcustom reason-indent-offset 2 + "Indent Reason code by this number of spaces." + :type 'integer + :group 'reason-mode + :safe #'integerp) + +(defun reason-looking-back-str (str) + "Like `looking-back' but for fixed strings rather than regexps. +Works around some regexp slowness. +Argument STR string to search for." + (let ((len (length str))) + (and (> (point) len) + (equal str (buffer-substring-no-properties (- (point) len) (point)))))) + +(defun reason-paren-level () + "Get the level of nesting inside parentheses." + (nth 0 (syntax-ppss))) + +(defun reason-in-str-or-cmnt () + "Return whether point is currently inside a string or a comment." + (nth 8 (syntax-ppss))) + +(defun reason-rewind-past-str-cmnt () + "Rewind past string or comment." + (goto-char (nth 8 (syntax-ppss)))) + +(defun reason-rewind-irrelevant () + "Rewind past irrelevant characters (whitespace of inside comments)." + (interactive) + (let ((starting (point))) + (skip-chars-backward "[:space:]\n") + (if (reason-looking-back-str "*/") (backward-char)) + (if (reason-in-str-or-cmnt) + (reason-rewind-past-str-cmnt)) + (if (/= starting (point)) + (reason-rewind-irrelevant)))) + +(defun reason-align-to-expr-after-brace () + "Align the expression at point to the expression after the previous brace." + (save-excursion + (forward-char) + ;; We don't want to indent out to the open bracket if the + ;; open bracket ends the line + (when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$")) + (when (looking-at "[[:space:]]") + (forward-word 1) + (backward-word 1)) + (current-column)))) + +(defun reason-align-to-prev-expr () + "Align the expression at point to the previous expression." + (let ((alignment (save-excursion + (forward-char) + ;; We don't want to indent out to the open bracket if the + ;; open bracket ends the line + (when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$")) + (if (looking-at "[[:space:]]") + (progn + (forward-word 1) + (backward-word 1)) + (backward-char)) + (current-column))))) + (if (not alignment) + (save-excursion + (forward-char) + (forward-line) + (back-to-indentation) + (current-column)) + alignment))) + +;;; Start of a reason binding +(defvar reason-binding + (regexp-opt '("let" "type" "module" "fun"))) + +(defun reason-beginning-of-defun (&optional arg) + "Move backward to the beginning of the current defun. + +With ARG, move backward multiple defuns. Negative ARG means +move forward. + +This is written mainly to be used as `beginning-of-defun-function'. +Don't move to the beginning of the line. `beginning-of-defun', +which calls this, does that afterwards." + (interactive "p") + (re-search-backward (concat "^\\(" reason-binding "\\)\\_>") + nil 'move (or arg 1))) + +(defun reason-end-of-defun () + "Move forward to the next end of defun. + +With argument, do it that many times. +Negative argument -N means move back to Nth preceding end of defun. + +Assume that this is called after ‘beginning-of-defun’. So point is +at the beginning of the defun body. + +This is written mainly to be used as `end-of-defun-function' for Reason." + (interactive) + ;; Find the opening brace + (if (re-search-forward "[{]" nil t) + (progn + (goto-char (match-beginning 0)) + ;; Go to the closing brace + (condition-case nil + (forward-sexp) + (scan-error + ;; The parentheses are unbalanced; instead of being unable to fontify, just jump to the end of the buffer + (goto-char (point-max))))) + ;; There is no opening brace, so consider the whole buffer to be one "defun" + (goto-char (point-max)))) + +(defun reason-rewind-to-beginning-of-current-level-expr () + "Rewind to the beginning of the expression on the current level of nesting." + (interactive) + (let ((current-level (reason-paren-level))) + (back-to-indentation) + (when (looking-at "=>") + (reason-rewind-irrelevant) + (back-to-indentation)) + (while (> (reason-paren-level) current-level) + (backward-up-list) + (back-to-indentation)))) + +(defun reason-mode-indent-line () + "Indent current line." + (interactive) + (let ((indent + (save-excursion + (back-to-indentation) + ;; Point is now at beginning of current line + (let* ((level (reason-paren-level)) + (baseline + ;; Our "baseline" is one level out from the indentation of the expression + ;; containing the innermost enclosing opening bracket. That + ;; way if we are within a block that has a different + ;; indentation than this mode would give it, we still indent + ;; the inside of it correctly relative to the outside. + (if (= 0 level) + 0 + (save-excursion + (reason-rewind-irrelevant) + (if (save-excursion + (reason-rewind-to-beginning-of-current-level-expr) + (looking-at "<")) + (progn + (reason-rewind-to-beginning-of-current-level-expr) + (current-column)) + (progn + (backward-up-list) + (reason-rewind-to-beginning-of-current-level-expr) + + (cond + ((looking-at "switch") + (current-column)) + + ((looking-at "|") + (+ (current-column) (* reason-indent-offset 2))) + + (t + (let ((current-level (reason-paren-level))) + (save-excursion + (while (and (= current-level (reason-paren-level)) + (not (looking-at reason-binding))) + (reason-rewind-irrelevant) + (reason-rewind-to-beginning-of-current-level-expr)) + (+ (current-column) reason-indent-offset))))))))))) + (cond + ;; A function return type is indented to the corresponding function arguments + ((looking-at "=>") + (+ baseline reason-indent-offset)) + + ((reason-in-str-or-cmnt) + (cond + ;; In the end of the block -- align with star + ((looking-at "*/") (+ baseline 1)) + ;; Indent to the following shape: + ;; /* abcd + ;; * asdf + ;; */ + ;; + ((looking-at "*") (+ baseline 1)) + ;; Indent to the following shape: + ;; /* abcd + ;; asdf + ;; */ + ;; + (t (+ baseline (+ reason-indent-offset 1))))) + + ((looking-at ""))) + (backward-up-list) + (reason-rewind-to-beginning-of-current-level-expr) + (cond + ((looking-at "switch") baseline) + + (jsx? (current-column)) + + (t (- baseline reason-indent-offset)))))) + + ;; Doc comments in /** style with leading * indent to line up the *s + ((and (nth 4 (syntax-ppss)) (looking-at "*")) + (+ 1 baseline)) + + ;; If we're in any other token-tree / sexp, then: + (t + (or + ;; If we are inside a pair of braces, with something after the + ;; open brace on the same line and ending with a comma, treat + ;; it as fields and align them. + (when (> level 0) + (save-excursion + (reason-rewind-irrelevant) + (backward-up-list) + ;; Point is now at the beginning of the containing set of braces + (reason-align-to-expr-after-brace))) + + (progn + (back-to-indentation) + (cond ((looking-at (regexp-opt '("and" "type"))) + baseline) + ((save-excursion + (reason-rewind-irrelevant) + (= (point) 1)) + baseline) + ((save-excursion + (while (looking-at "|") + (reason-rewind-irrelevant) + (back-to-indentation)) + (looking-at (regexp-opt '("type")))) + (+ baseline reason-indent-offset)) + ((looking-at "|\\|/[/*]") + baseline) + ((and (> level 0) + (save-excursion + (reason-rewind-irrelevant) + (backward-up-list) + (reason-rewind-to-beginning-of-current-level-expr) + (looking-at "switch"))) + (+ baseline reason-indent-offset)) + ((save-excursion + (reason-rewind-irrelevant) + (looking-back "[{;,\\[(]" (- (point) 2))) + baseline) + ((and + (save-excursion + (reason-rewind-irrelevant) + (reason-rewind-to-beginning-of-current-level-expr) + (and (looking-at reason-binding) + (not (progn + (forward-sexp) + (forward-sexp) + (skip-chars-forward "[:space:]\n") + (looking-at "="))))) + (not (save-excursion + (skip-chars-backward "[:space:]\n") + (reason-looking-back-str "=>")))) + (save-excursion + (reason-rewind-irrelevant) + (backward-sexp) + (reason-align-to-prev-expr))) + ((save-excursion + (reason-rewind-irrelevant) + (looking-back "<\/.*?>" (- (point) 30))) + baseline) + (t + (save-excursion + (reason-rewind-irrelevant) + (reason-rewind-to-beginning-of-current-level-expr) + + (if (looking-at "|") + baseline + (+ baseline reason-indent-offset))))) + ;; Point is now at the beginning of the current line + )))))))) + + (when indent + ;; If we're at the beginning of the line (before or at the current + ;; indentation), jump with the indentation change. Otherwise, save the + ;; excursion so that adding the indentations will leave us at the + ;; equivalent position within the line to where we were before. + (if (<= (current-column) (current-indentation)) + (indent-line-to indent) + (save-excursion (indent-line-to indent)))))) + +(provide 'reason-indent) + +;;; reason-indent.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/vendor/reason-interaction.el b/users/wpcarro/emacs/.emacs.d/vendor/reason-interaction.el new file mode 100644 index 000000000..6ceaed1e9 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/vendor/reason-interaction.el @@ -0,0 +1,216 @@ +;;; reason-interaction.el --- Phrase navitagion for rtop -*-lexical-binding: t-*- + +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;;; Commentary: + +;; Phrase navigation for utop and maybe other REPLs. + +;; The utop compatibility layer for Reason was mainly taken from: +;; https://github.com/ocaml/tuareg/blob/master/tuareg-light.el (big thanks!) + +;;; Code: + +(defun reason-backward-char (&optional step) + "Go back one char. +Similar to `backward-char` but it does not signal errors +`beginning-of-buffer` and `end-of-buffer`. It optionally takes a +STEP parameter for jumping back more than one character." + (when step (goto-char (- (point) step)) + (goto-char (1- (point))))) + +(defun reason-forward-char (&optional step) + "Go forward one char. +Similar to `forward-char` but it does not signal errors +`beginning-of-buffer` and `end-of-buffer`. It optionally takes a +STEP parameter for jumping back more than one character." + (when step (goto-char (+ (point) step)) + (goto-char (1+ (point))))) + +(defun reason-in-literal-p () + "Return non-nil if point is inside an Reason literal." + (nth 3 (syntax-ppss))) + +(defconst reason-comment-delimiter-regexp "\\*/\\|/\\*" + "Regex for identify either open or close comment delimiters.") + +(defun reason-in-between-comment-chars-p () + "Return non-nil iff point is in between the comment delimiter chars. +It returns non-nil if point is between the chars only (*|/ or /|* +where | is point)." + (and (not (bobp)) (not (eobp)) + (or (and (char-equal ?/ (char-before)) (char-equal ?* (char-after))) + (and (char-equal ?* (char-before)) (char-equal ?/ (char-after)))))) + +(defun reason-looking-at-comment-delimiters-p () + "Return non-nil iff point in between comment delimiters." + (looking-at-p reason-comment-delimiter-regexp)) + +(defun reason-in-between-comment-delimiters-p () + "Return non-nil if inside /* and */." + (nth 4 (syntax-ppss))) + +(defun reason-in-comment-p () + "Return non-nil iff point is inside or right before a comment." + (or (reason-in-between-comment-delimiters-p) + (reason-in-between-comment-chars-p) + (reason-looking-at-comment-delimiters-p))) + +(defun reason-beginning-of-literal-or-comment () + "Skip to the beginning of the current literal or comment (or buffer)." + (interactive) + (goto-char (or (nth 8 (syntax-ppss)) (point)))) + +(defun reason-inside-block-scope-p () + "Skip to the beginning of the current literal or comment (or buffer)." + (and (> (nth 0 (syntax-ppss)) 0) + (let ((delim-start (nth 1 (syntax-ppss)))) + (save-excursion + (goto-char delim-start) + (char-equal ?{ (following-char)))))) + +(defun reason-at-phrase-break-p () + "Is the underlying `;' a phrase break?" + ;; Difference from OCaml, the phrase separator is a single semi-colon + (and (not (eobp)) + (char-equal ?\; (following-char)))) + +(defun reason-skip-to-close-delimiter (&optional limit) + "Skip to the end of a Reason block. +It basically calls `re-search-forward` in order to go to any +closing delimiter, not concerning itself with balancing of any +sort. Client code needs to check that. +LIMIT is passed to `re-search-forward` directly." + (re-search-forward "\\s)" limit 'move)) + +(defun reason-skip-back-to-open-delimiter (&optional limit) + "Skip to the beginning of a Reason block backwards. +It basically calls `re-search-backward` in order to go to any +opening delimiter, not concerning itself with balancing of any +sort. Client code needs to check that. +LIMIT is passed to `re-search-backward` directly." + (re-search-backward "\\s(" limit 'move)) + +(defun reason-find-phrase-end () + "Skip to the end of a phrase." + (while (and (not (eobp)) + (not (reason-at-phrase-break-p))) + (if (re-search-forward ";" nil 'move) + (progn (when (reason-inside-block-scope-p) + (reason-skip-to-close-delimiter)) + (goto-char (1- (point)))) + ;; avoid infinite loop at the end of the buffer + (re-search-forward "[[:space:]\\|\n]+" nil 'move))) + (min (goto-char (1+ (point))) (point-max))) + +(defun reason-skip-blank-and-comments () + "Skip blank spaces and comments." + (cond + ((eobp) (point)) + ((or (reason-in-between-comment-chars-p) + (reason-looking-at-comment-delimiters-p)) (progn + (reason-forward-char 1) + (reason-skip-blank-and-comments))) + ((reason-in-between-comment-delimiters-p) (progn + (search-forward "*/" nil t) + (reason-skip-blank-and-comments))) + ((eolp) (progn + (reason-forward-char 1) + (reason-skip-blank-and-comments))) + (t (progn (skip-syntax-forward " ") + (point))))) + +(defun reason-skip-back-blank-and-comments () + "Skip blank spaces and comments backwards." + (cond + ((bobp) (point)) + ((looking-back reason-comment-delimiter-regexp) (progn + (reason-backward-char 1) + (reason-skip-back-blank-and-comments))) + ((reason-in-between-comment-delimiters-p) (progn + (search-backward "/*" nil t) + (reason-backward-char 1) + (reason-skip-back-blank-and-comments))) + ((or (reason-in-between-comment-chars-p) + (reason-looking-at-comment-delimiters-p)) (progn + (reason-backward-char 1) + (reason-skip-back-blank-and-comments))) + ((bolp) (progn + (reason-backward-char 1) + (reason-skip-back-blank-and-comments))) + (t (progn (skip-syntax-backward " ") + (point))))) + +(defun reason-ro (&rest words) + "Build a regex matching iff at least a word in WORDS is present." + (concat "\\<" (regexp-opt words t) "\\>")) + +(defconst reason-find-phrase-beginning-regexp + (concat (reason-ro "end" "type" "module" "sig" "struct" "class" + "exception" "open" "let") + "\\|^#[ \t]*[a-z][_a-z]*\\>\\|;")) + +(defun reason-at-phrase-start-p () + "Return t if is looking at the beginning of a phrase. +A phrase starts when a toplevel keyword is at the beginning of a line." + (or (looking-at "#") + (looking-at reason-find-phrase-beginning-regexp))) + +(defun reason-find-phrase-beginning-backward () + "Find the beginning of a phrase and return point. +It scans code backwards, therefore the caller can assume that the +beginning of the phrase (if found) is always before the starting +point. No error is signalled and (point-min) is returned when a +phrease cannot be found." + (beginning-of-line) + (while (and (not (bobp)) (not (reason-at-phrase-start-p))) + (if (reason-inside-block-scope-p) + (reason-skip-back-to-open-delimiter) + (re-search-backward reason-find-phrase-beginning-regexp nil 'move))) + (point)) + +(defun reason-discover-phrase () + "Discover a Reason phrase in the buffer." + ;; TODO reason-with-internal-syntax ;; tuareg2 modifies the syntax table (removed for now) + ;; TODO stop-at-and feature for phrase detection (do we need it?) + ;; TODO tuareg2 has some custom logic for module and class (do we need it?) + (save-excursion + (let ((case-fold-search nil)) + (reason-skip-blank-and-comments) + (list (reason-find-phrase-beginning-backward) ;; beginning + (reason-find-phrase-end) ;; end + (save-excursion ;; end-with-comment + (reason-skip-blank-and-comments) + (point)))))) + +(defun reason-discover-phrase-debug () + "Discover a Reason phrase in the buffer (debug mode)." + (let ((triple (reason-discover-phrase))) + (message (concat "Evaluating: \"" (reason-fetch-phrase triple) "\"")) + triple)) + +(defun reason-fetch-phrase (triple) + "Fetch the phrase text given a TRIPLE." + (let* ((start (nth 0 triple)) + (end (nth 1 triple))) ;; we don't need end-with-comment + (buffer-substring-no-properties start end))) + +(defun reason-next-phrase () + "Skip to the beginning of the next phrase." + (cond + ((reason-at-phrase-start-p) (point)) + ((eolp) (progn + (forward-char 1) + (reason-skip-blank-and-comments) + (reason-next-phrase))) + ((reason-inside-block-scope-p) (progn (reason-skip-to-close-delimiter) + (reason-next-phrase))) + ((looking-at ";") (progn + (forward-char 1) + (reason-next-phrase))) + (t (progn (end-of-line) + (reason-next-phrase))))) + +(provide 'reason-interaction) + +;;; reason-interaction.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/vendor/reason-mode.el b/users/wpcarro/emacs/.emacs.d/vendor/reason-mode.el new file mode 100644 index 000000000..789735955 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/vendor/reason-mode.el @@ -0,0 +1,242 @@ +;;; reason-mode.el --- A major mode for editing ReasonML -*-lexical-binding: t-*- +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;; Version: 0.4.0 +;; Author: Mozilla +;; Url: https://github.com/reasonml-editor/reason-mode +;; Keywords: languages, ocaml +;; Package-Requires: ((emacs "24.3")) + +;; This file is NOT part of GNU Emacs. + +;; This file is distributed under the terms of both the MIT license and the +;; Apache License (version 2.0). + +;;; Commentary: +;; This project provides useful functions and helpers for developing code +;; using the Reason programming language (https://facebook.github.io/reason). +;; +;; Reason is an umbrella project that provides a curated layer for OCaml. +;; +;; It offers: +;; - A new, familiar syntax for the battle-tested language that is OCaml. +;; - A workflow for compiling to JavaScript and native code. +;; - A set of friendly documentations, libraries and utilities. +;; +;; See the README.md for more details. + +;;; Code: + +(require 'reason-indent) +(require 'refmt) +(require 'reason-interaction) + +(eval-when-compile (require 'rx) + (require 'compile) + (require 'url-vars)) + +;; Syntax definitions and helpers +(defvar reason-mode-syntax-table + (let ((table (make-syntax-table))) + + ;; Operators + (dolist (i '(?+ ?- ?* ?/ ?& ?| ?^ ?! ?< ?> ?~ ?@)) + (modify-syntax-entry i "." table)) + + ;; Strings + (modify-syntax-entry ?\" "\"" table) + (modify-syntax-entry ?\\ "\\" table) + (modify-syntax-entry ?\' "_" table) + + ;; Comments + (modify-syntax-entry ?/ ". 124b" table) + (modify-syntax-entry ?* ". 23n" table) + (modify-syntax-entry ?\n "> b" table) + (modify-syntax-entry ?\^m "> b" table) + + table)) + +(defgroup reason nil + "Support for Reason code." + :link '(url-link "http://facebook.github.io/reason/") + :group 'languages) + +(defcustom reason-mode-hook nil + "Hook called by `reason-mode'." + :type 'hook + :group 'reason) + +;; Font-locking definitions and helpers +(defconst reason-mode-keywords + '("and" "as" + "else" "external" + "fun" "for" + "if" "impl" "in" "include" + "let" + "module" "match" "mod" "move" "mutable" + "open" + "priv" "pub" + "rec" "ref" "return" + "self" "static" "switch" "struct" "super" + "trait" "type" + "use" + "virtual" + "where" "when" "while")) + +(defconst reason-mode-consts + '("true" "false")) + +(defconst reason-special-types + '("int" "float" "string" "char" + "bool" "unit" "list" "array" "exn" + "option" "ref")) + +(defconst reason-camel-case + (rx symbol-start + (group upper (0+ (any word nonascii digit "_"))) + symbol-end)) + +(eval-and-compile + (defconst reason--char-literal-rx + (rx (seq (group "'") + (or (seq "\\" anything) + (not (any "'\\"))) + (group "'"))))) + +(defun reason-re-word (inner) + "Build a word regexp given INNER." + (concat "\\<" inner "\\>")) + +(defun reason-re-grab (inner) + "Build a grab regexp given INNER." + (concat "\\(" inner "\\)")) + +(defun reason-regexp-opt-symbols (words) + "Like `(regexp-opt words 'symbols)`, but will work on Emacs 23. +See rust-mode PR #42. +Argument WORDS argument to pass to `regexp-opt`." + (concat "\\_<" (regexp-opt words t) "\\_>")) + +;;; Syntax highlighting for Reason +(defvar reason-font-lock-keywords + `((,(reason-regexp-opt-symbols reason-mode-keywords) . font-lock-keyword-face) + (,(reason-regexp-opt-symbols reason-special-types) . font-lock-builtin-face) + (,(reason-regexp-opt-symbols reason-mode-consts) . font-lock-constant-face) + + (,reason-camel-case 1 font-lock-type-face) + + ;; Field names like `foo:`, highlight excluding the : + (,(concat (reason-re-grab reason-re-ident) ":[^:]") 1 font-lock-variable-name-face) + ;; Module names like `foo::`, highlight including the :: + (,(reason-re-grab (concat reason-re-ident "::")) 1 font-lock-type-face) + ;; Name punned labeled args like ::foo + (,(concat "[[:space:]]+" (reason-re-grab (concat "::" reason-re-ident))) 1 font-lock-type-face) + + ;; TODO jsx attribs? + (, + (concat "<[/]?" (reason-re-grab reason-re-ident) "[^>]*" ">") + 1 font-lock-type-face))) + +(defun reason-mode-try-find-alternate-file (mod-name extension) + "Switch to the file given by MOD-NAME and EXTENSION." + (let* ((filename (concat mod-name extension)) + (buffer (get-file-buffer filename))) + (if buffer (switch-to-buffer buffer) + (find-file filename)))) + +(defun reason-mode-find-alternate-file () + "Switch to implementation/interface file." + (interactive) + (let ((name buffer-file-name)) + (when (string-match "\\`\\(.*\\)\\.re\\([il]\\)?\\'" name) + (let ((mod-name (match-string 1 name)) + (e (match-string 2 name))) + (cond + ((string= e "i") + (reason-mode-try-find-alternate-file mod-name ".re")) + (t + (reason-mode-try-find-alternate-file mod-name ".rei"))))))) + +(defun reason--syntax-propertize-multiline-string (end) + "Propertize Reason multiline string. +Argument END marks the end of the string." + (let ((ppss (syntax-ppss))) + (when (eq t (nth 3 ppss)) + (let ((key (save-excursion + (goto-char (nth 8 ppss)) + (and (looking-at "{\\([a-z]*\\)|") + (match-string 1))))) + (when (search-forward (format "|%s}" key) end 'move) + (put-text-property (1- (match-end 0)) (match-end 0) + 'syntax-table (string-to-syntax "|"))))))) + +(defun reason-syntax-propertize-function (start end) + "Propertize Reason function. +Argument START marks the beginning of the function. +Argument END marks the end of the function." + (goto-char start) + (reason--syntax-propertize-multiline-string end) + (funcall + (syntax-propertize-rules + (reason--char-literal-rx (1 "\"") (2 "\"")) + ;; multi line strings + ("\\({\\)[a-z]*|" + (1 (prog1 "|" + (goto-char (match-end 0)) + (reason--syntax-propertize-multiline-string end))))) + (point) end)) + +(defvar reason-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\C-c\C-a" #'reason-mode-find-alternate-file) + (define-key map "\C-c\C-r" #'refmt-region-ocaml-to-reason) + (define-key map "\C-c\C-o" #'refmt-region-reason-to-ocaml) + map)) + +;;;###autoload +(define-derived-mode reason-mode prog-mode "Reason" + "Major mode for Reason code. + +\\{reason-mode-map}" + :group 'reason + :syntax-table reason-mode-syntax-table + :keymap reason-mode-map + + ;; Syntax + (setq-local syntax-propertize-function #'reason-syntax-propertize-function) + ;; Indentation + (setq-local indent-line-function 'reason-mode-indent-line) + ;; Fonts + (setq-local font-lock-defaults '(reason-font-lock-keywords)) + ;; Misc + (setq-local comment-start "/*") + (setq-local comment-end "*/") + (setq-local indent-tabs-mode nil) + ;; Allow paragraph fills for comments + (setq-local comment-start-skip "/\\*+[ \t]*") + (setq-local paragraph-start + (concat "^[ \t]*$\\|\\*)$\\|" page-delimiter)) + (setq-local paragraph-separate paragraph-start) + (setq-local require-final-newline t) + (setq-local normal-auto-fill-function nil) + (setq-local comment-multi-line t) + + (setq-local beginning-of-defun-function 'reason-beginning-of-defun) + (setq-local end-of-defun-function 'reason-end-of-defun) + (setq-local parse-sexp-lookup-properties t)) + +;;;###autoload +(add-to-list 'auto-mode-alist '("\\.rei?\\'" . reason-mode)) + +(defun reason-mode-reload () + "Reload Reason mode." + (interactive) + (unload-feature 'reason-mode) + (unload-feature 'reason-indent) + (unload-feature 'reason-interaction) + (require 'reason-mode) + (reason-mode)) + +(provide 'reason-mode) + +;;; reason-mode.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/vendor/refmt.el b/users/wpcarro/emacs/.emacs.d/vendor/refmt.el new file mode 100644 index 000000000..b9ea2b43f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/vendor/refmt.el @@ -0,0 +1,231 @@ +;;; refmt.el --- utility functions to format reason code + +;; Copyright (c) 2014 The go-mode Authors. All rights reserved. +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions are +;; met: + +;; * Redistributions of source code must retain the above copyright +;; notice, this list of conditions and the following disclaimer. +;; * Redistributions in binary form must reproduce the above +;; copyright notice, this list of conditions and the following disclaimer +;; in the documentation and/or other materials provided with the +;; distribution. +;; * Neither the name of the copyright holder nor the names of its +;; contributors may be used to endorse or promote products derived from +;; this software without specific prior written permission. + +;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.) + +;;; Commentary: +;; + +;;; Code: + +(require 'cl-lib) + +(defcustom refmt-command "refmt" + "The 'refmt' command." + :type 'string + :group 're-fmt) + +(defcustom refmt-show-errors 'buffer + "Where to display refmt error output. +It can either be displayed in its own buffer, in the echo area, or not at all. +Please note that Emacs outputs to the echo area when writing +files and will overwrite refmt's echo output if used from inside +a `before-save-hook'." + :type '(choice + (const :tag "Own buffer" buffer) + (const :tag "Echo area" echo) + (const :tag "None" nil)) + :group 're-fmt) + +(defcustom refmt-width-mode nil + "Specify width when formatting buffer contents." + :type '(choice + (const :tag "Window width" window) + (const :tag "Fill column" fill) + (const :tag "None" nil)) + :group 're-fmt) + +;;;###autoload +(defun refmt-before-save () + "Add this to .emacs to run refmt on the current buffer when saving: + (add-hook 'before-save-hook 'refmt-before-save)." + (interactive) + (when (eq major-mode 'reason-mode) (refmt))) + +(defun reason--goto-line (line) + (goto-char (point-min)) + (forward-line (1- line))) + +(defun reason--delete-whole-line (&optional arg) + "Delete the current line without putting it in the `kill-ring'. +Derived from function `kill-whole-line'. ARG is defined as for that +function." + (setq arg (or arg 1)) + (if (and (> arg 0) + (eobp) + (save-excursion (forward-visible-line 0) (eobp))) + (signal 'end-of-buffer nil)) + (if (and (< arg 0) + (bobp) + (save-excursion (end-of-visible-line) (bobp))) + (signal 'beginning-of-buffer nil)) + (cond ((zerop arg) + (delete-region (progn (forward-visible-line 0) (point)) + (progn (end-of-visible-line) (point)))) + ((< arg 0) + (delete-region (progn (end-of-visible-line) (point)) + (progn (forward-visible-line (1+ arg)) + (unless (bobp) + (backward-char)) + (point)))) + (t + (delete-region (progn (forward-visible-line 0) (point)) + (progn (forward-visible-line arg) (point)))))) + +(defun reason--apply-rcs-patch (patch-buffer &optional start-pos) + "Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer." + (setq start-pos (or start-pos (point-min))) + (let ((first-line (line-number-at-pos start-pos)) + (target-buffer (current-buffer)) + ;; Relative offset between buffer line numbers and line numbers + ;; in patch. + ;; + ;; Line numbers in the patch are based on the source file, so + ;; we have to keep an offset when making changes to the + ;; buffer. + ;; + ;; Appending lines decrements the offset (possibly making it + ;; negative), deleting lines increments it. This order + ;; simplifies the forward-line invocations. + (line-offset 0)) + (save-excursion + (with-current-buffer patch-buffer + (goto-char (point-min)) + (while (not (eobp)) + (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)") + (error "invalid rcs patch or internal error in reason--apply-rcs-patch")) + (forward-line) + (let ((action (match-string 1)) + (from (string-to-number (match-string 2))) + (len (string-to-number (match-string 3)))) + (cond + ((equal action "a") + (let ((start (point))) + (forward-line len) + (let ((text (buffer-substring start (point)))) + (with-current-buffer target-buffer + (cl-decf line-offset len) + (goto-char start-pos) + (forward-line (- from len line-offset)) + (insert text))))) + ((equal action "d") + (with-current-buffer target-buffer + (reason--goto-line (- (1- (+ first-line from)) line-offset)) + (cl-incf line-offset len) + (reason--delete-whole-line len))) + (t + (error "invalid rcs patch or internal error in reason--apply-rcs-patch"))))))))) + +(defun refmt--process-errors (filename tmpfile errorfile errbuf) + (with-current-buffer errbuf + (if (eq refmt-show-errors 'echo) + (progn + (message "%s" (buffer-string)) + (refmt--kill-error-buffer errbuf)) + (insert-file-contents errorfile nil nil nil) + ;; Convert the refmt stderr to something understood by the compilation mode. + (goto-char (point-min)) + (insert "refmt errors:\n") + (while (search-forward-regexp (regexp-quote tmpfile) nil t) + (replace-match (file-name-nondirectory filename))) + (compilation-mode) + (display-buffer errbuf)))) + +(defun refmt--kill-error-buffer (errbuf) + (let ((win (get-buffer-window errbuf))) + (if win + (quit-window t win) + (with-current-buffer errbuf + (erase-buffer)) + (kill-buffer errbuf)))) + +(defun apply-refmt (&optional start end from to) + (setq start (or start (point-min)) + end (or end (point-max)) + from (or from "re") + to (or to "re")) + (let* ((ext (file-name-extension buffer-file-name t)) + (bufferfile (make-temp-file "refmt" nil ext)) + (outputfile (make-temp-file "refmt" nil ext)) + (errorfile (make-temp-file "refmt" nil ext)) + (errbuf (if refmt-show-errors (get-buffer-create "*Refmt Errors*"))) + (patchbuf (get-buffer-create "*Refmt patch*")) + (coding-system-for-read 'utf-8) + (coding-system-for-write 'utf-8) + (width-args + (cond + ((equal refmt-width-mode 'window) + (list "--print-width" (number-to-string (window-body-width)))) + ((equal refmt-width-mode 'fill) + (list "--print-width" (number-to-string fill-column))) + (t + '())))) + (unwind-protect + (save-restriction + (widen) + (write-region start end bufferfile) + (if errbuf + (with-current-buffer errbuf + (setq buffer-read-only nil) + (erase-buffer))) + (with-current-buffer patchbuf + (erase-buffer)) + (if (zerop (apply 'call-process + refmt-command nil (list (list :file outputfile) errorfile) + nil (append width-args (list "--parse" from "--print" to bufferfile)))) + (progn + (call-process-region start end "diff" nil patchbuf nil "-n" "-" + outputfile) + (reason--apply-rcs-patch patchbuf start) + (message "Applied refmt") + (if errbuf (refmt--kill-error-buffer errbuf))) + (message "Could not apply refmt") + (if errbuf + (refmt--process-errors (buffer-file-name) bufferfile errorfile errbuf))))) + (kill-buffer patchbuf) + (delete-file errorfile) + (delete-file bufferfile) + (delete-file outputfile))) + +(defun refmt () + "Format the current buffer according to the refmt tool." + (interactive) + (apply-refmt)) + +(defun refmt-region-ocaml-to-reason (start end) + (interactive "r") + (apply-refmt start end "ml")) + +(defun refmt-region-reason-to-ocaml (start end) + (interactive "r") + (apply-refmt start end "re" "ml")) + +(provide 'refmt) + +;;; refmt.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/>.el b/users/wpcarro/emacs/.emacs.d/wpc/>.el new file mode 100644 index 000000000..68d8576b8 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/>.el @@ -0,0 +1,29 @@ +;;; >.el --- Small utility functions -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Originally I stored the `>>` macro in macros.el, but after setting up linting +;; for my Elisp in CI, `>>` failed because it didn't have the `macros-` +;; namespace. I created this module to establish a `>-` namespace under which I +;; can store some utilities that would be best kept without a cumbersome +;; namespace. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro >-> (&rest forms) + "Compose a new, point-free function by composing FORMS together." + (let ((sym (gensym))) + `(lambda (,sym) + (->> ,sym ,@forms)))) + + +(provide '>) +;;; >.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/al.el b/users/wpcarro/emacs/.emacs.d/wpc/al.el new file mode 100644 index 000000000..e29f853f8 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/al.el @@ -0,0 +1,255 @@ +;;; al.el --- Interface for working with associative lists -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Firstly, a rant: +;; In most cases, I find Elisp's APIs to be confusing. There's a mixture of +;; overloaded functions that leak the implementation details (TODO: provide an +;; example of this.) of the abstract data type, which I find privileges those +;; "insiders" who spend disproportionately large amounts of time in Elisp land, +;; and other functions with little-to-no pattern about the order in which +;; arguments should be applied. In theory, however, most of these APIs could +;; and should be much simpler. This module represents a step in that direction. +;; +;; I'm modelling these APIs after Elixir's APIs. +;; +;; On my wishlist is to create protocols that will allow generic interfaces like +;; Enum protocols, etc. Would be nice to abstract over... +;; - associative lists (i.e. alists) +;; - property lists (i.e. plists) +;; - hash tables +;; ...with some dictionary or map-like interface. This will probably end up +;; being quite similar to the kv.el project but with differences at the API +;; layer. +;; +;; Similar libraries: +;; - map.el: Comes bundled with recent versions of Emacs. +;; - asoc.el: Helpers for working with alists. asoc.el is similar to alist.el +;; because it uses the "!" convention for signalling that a function mutates +;; the underlying data structure. +;; - ht.el: Hash table library. +;; - kv.el: Library for dealing with key-value collections. Note that map.el +;; has a similar typeclass because it works with lists, hash-tables, or +;; arrays. +;; - a.el: Clojure-inspired way of working with key-value data structures in +;; Elisp. Works with alists, hash-tables, and sometimes vectors. +;; +;; Some API design principles: +;; - The "noun" (i.e. alist) of the "verb" (i.e. function) comes last to improve +;; composability with the threading macro (i.e. `->>') and to improve consumers' +;; intuition with the APIs. Learn this once, know it always. +;; +;; - Every function avoids mutating the alist unless it ends with !. +;; +;; - CRUD operations will be named according to the following table: +;; - "create" *and* "set" +;; - "read" *and* "get" +;; - "update" +;; - "delete" *and* "remove" +;; +;; For better or worse, all of this code expects alists in the form of: +;; ((first-name . "William") (last-name . "Carroll")) +;; +;; Special thanks to github.com/alphapapa/emacs-package-dev-handbook for some of +;; the idiomatic ways to update alists. +;; +;; TODO: Include a section that compares alist.el to a.el from +;; github.com/plexus/a.el. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies: +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) +(require 'dash) +(require 'tuple) +(require 'maybe) + +;; TODO: Support function aliases for: +;; - create/set +;; - read/get +;; - update +;; - delete/remove + +;; Support mutative variants of functions with an ! appendage to their name. + +;; Ensure that the same message about only updating the first occurrence of a +;; key is consistent throughout documentation using string interpolation or some +;; other mechanism. + +;; TODO: Consider wrapping all of this with `(cl-defstruct alist xs)'. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst al-enable-tests? t + "When t, run the test suite.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support a variadic version of this to easily construct alists. +(defun al-new () + "Return a new, empty alist." + '()) + +;; Create +;; TODO: See if this mutates. +(defun al-set (k v xs) + "Set K to V in XS." + (if (al-has-key? k xs) + (progn + ;; Note: this is intentional `alist-get' and not `al-get'. + (setf (alist-get k xs) v) + xs) + (list-cons `(,k . ,v) xs))) + +(defun al-set! (k v xs) + "Set K to V in XS mutatively. +Note that this doesn't append to the alist in the way that most alists handle + writing. If the k already exists in XS, it is overwritten." + (map-delete xs k) + (map-put! xs k v)) + +;; Read +(defun al-get (k xs) + "Return the value at K in XS; otherwise, return nil. +Returns the first occurrence of K in XS since alists support multiple entries." + (cdr (assoc k xs))) + +(defun al-get-entry (k xs) + "Return the first key-value pair at K in XS." + (assoc k xs)) + +;; Update +;; TODO: Add warning about only the first occurrence being updated in the +;; documentation. +(defun al-update (k f xs) + "Apply F to the value stored at K in XS. +If `K' is not in `XS', this function errors. Use `al-upsert' if you're +interested in inserting a value when a key doesn't already exist." + (if (not (al-has-key? k xs)) + (error "Refusing to update: key does not exist in alist") + (al-set k (funcall f (al-get k xs)) xs))) + +(defun al-update! (k f xs) + "Call F on the entry at K in XS. +Mutative variant of `al-update'." + (al-set! k (funcall f (al-get k xs))xs)) + +;; TODO: Support this. +(defun al-upsert (k v f xs) + "If K exists in `XS' call `F' on the value otherwise insert `V'." + (if (al-has-key? k xs) + (al-update k f xs) + (al-set k v xs))) + +;; Delete +;; TODO: Make sure `delete' and `remove' behave as advertised in the Elisp docs. +(defun al-delete (k xs) + "Deletes the entry of K from XS. +This only removes the first occurrence of K, since alists support multiple + key-value entries. See `al-delete-all' and `al-dedupe'." + (remove (assoc k xs) xs)) + +(defun al-delete! (k xs) + "Delete the entry of K from XS. +Mutative variant of `al-delete'." + (delete (assoc k xs) xs)) + +;; Additions to the CRUD API +;; TODO: Implement this function. +(defun al-dedupe-keys (xs) + "Remove the entries in XS where the keys are `equal'.") + +(defun al-dedupe-entries (xs) + "Remove the entries in XS where the key-value pair are `equal'." + (delete-dups xs)) + +(defun al-keys (xs) + "Return a list of the keys in XS." + (mapcar 'car xs)) + +(defun al-values (xs) + "Return a list of the values in XS." + (mapcar 'cdr xs)) + +(defun al-has-key? (k xs) + "Return t if XS has a key `equal' to K." + (maybe-some? (assoc k xs))) + +(defun al-has-value? (v xs) + "Return t if XS has a value of V." + (maybe-some? (rassoc v xs))) + +(defun al-count (xs) + "Return the number of entries in XS." + (length xs)) + +;; TODO: Should I support `al-find-key' and `al-find-value' variants? +(defun al-find (p xs) + "Find an element in XS. + +Apply a predicate fn, P, to each key and value in XS and return the key of the +first element that returns t." + (let ((result (list-find (lambda (x) (funcall p (car x) (cdr x))) xs))) + (if result + (car result) + nil))) + +(defun al-map-keys (f xs) + "Call F on the values in XS, returning a new alist." + (list-map (lambda (x) + `(,(funcall f (car x)) . ,(cdr x))) + xs)) + +(defun al-map-values (f xs) + "Call F on the values in XS, returning a new alist." + (list-map (lambda (x) + `(,(car x) . ,(funcall f (cdr x)))) + xs)) + +(defun al-reduce (acc f xs) + "Return a new alist by calling F on k v and ACC from XS. +F should return a tuple. See tuple.el for more information." + (->> (al-keys xs) + (list-reduce acc + (lambda (k acc) + (funcall f k (al-get k xs) acc))))) + +(defun al-merge (a b) + "Return a new alist with a merge of alists, A and B. +In this case, the last writer wins, which is B." + (al-reduce a #'al-set b)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when al-enable-tests? + (prelude-assert + (equal '((2 . one) + (3 . two)) + (al-map-keys #'1+ + '((1 . one) + (2 . two))))) + (prelude-assert + (equal '((one . 2) + (two . 3)) + (al-map-values #'1+ + '((one . 1) + (two . 2)))))) + + +;; TODO: Support test cases for the entire API. + +(provide 'al) +;;; al.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/bag.el b/users/wpcarro/emacs/.emacs.d/wpc/bag.el new file mode 100644 index 000000000..38a09d94f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/bag.el @@ -0,0 +1,71 @@ +;;; bag.el --- Working with bags (aka multi-sets) -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; What is a bag? A bag should be thought of as a frequency table. It's a way +;; to convert a list of something into a set that allows duplicates. Isn't +;; allowing duplicates the whole thing with Sets? Kind of. But the interface +;; of Sets is something that bags resemble, so multi-set isn't as bag of a name +;; as it may first seem. +;; +;; If you've used Python's collections.Counter, the concept of a bag should be +;; familiar already. +;; +;; Interface: +;; - add :: x -> Bag(x) -> Bag(x) +;; - remove :: x -> Bag(x) -> Bag(x) +;; - union :: Bag(x) -> Bag(x) -> Bag(x) +;; - difference :: Bag(x) -> Bag(x) -> Bag(x) + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'al) +(require 'number) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct bag xs) + +(defun bag-update (f xs) + "Call F on alist in XS." + (let ((ys (bag-xs xs))) + (setf (bag-xs xs) (funcall f ys)))) + +(defun bag-new () + "Create an empty bag." + (make-bag :xs (al-new))) + +(defun bag-contains? (x xs) + "Return t if XS has X." + (al-has-key? x (bag-xs xs))) + +;; TODO: Tabling this for now since working with structs seems to be +;; disappointingly difficult. Where is `struct-update'? +;; (defun bag-add (x xs) +;; "Add X to XS.") + +;; TODO: What do we name delete vs. remove? +;; (defun bag-remove (x xs) +;; "Remove X from XS. +;; This is a no-op is X doesn't exist in XS.") + +(defun bag-from-list (xs) + "Map a list of `XS' into a bag." + (->> xs + (list-reduce + (bag-new) + (lambda (x acc) + (bag-add x 1 #'number-inc acc))))) + +(provide 'bag) +;;; bag.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/bookmark.el b/users/wpcarro/emacs/.emacs.d/wpc/bookmark.el new file mode 100644 index 000000000..76fc6fe4d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/bookmark.el @@ -0,0 +1,100 @@ +;;; bookmark.el --- Saved files and directories on my filesystem -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; After enjoying and relying on Emacs's builtin `jump-to-register' command, I'd +;; like to recreate this functionality with a few extensions. +;; +;; Everything herein will mimmick my previous KBDs for `jump-to-register', which +;; were -j-. If the `bookmark-path' is a file, Emacs will +;; open a buffer with that file. If the `bookmark-path' is a directory, Emacs +;; will open an ivy window searching that directory. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'buffer) +(require 'list) +(require 'string) +(require 'set) +(require 'constants) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct bookmark label path kbd) + +;; TODO: Consider hosting this function somewhere other than here, since it +;; feels useful above of the context of bookmarks. +;; TODO: Assess whether it'd be better to use the existing function: +;; `counsel-projectile-switch-project-action'. See the noise I made on GH for +;; more context: https://github.com/ericdanan/counsel-projectile/issues/137 + +(defun bookmark-handle-directory-dwim (path) + "Open PATH as either a project directory or a regular directory. +If PATH is `projectile-project-p', open with `counsel-projectile-find-file'. +Otherwise, open with `counsel-find-file'." + (if (projectile-project-p path) + (with-temp-buffer + (cd (projectile-project-p path)) + (call-interactively #'counsel-projectile-find-file)) + (let ((ivy-extra-directories nil)) + (counsel-find-file path)))) + +(defconst bookmark-handle-directory #'bookmark-handle-directory-dwim + "Function to call when a bookmark points to a directory.") + +(defconst bookmark-handle-file #'counsel-find-file-action + "Function to call when a bookmark points to a file.") + +(defconst bookmark-whitelist + (list + (make-bookmark :label "briefcase" + :path constants-briefcase + :kbd "b") + (make-bookmark :label "current project" + :path constants-current-project + :kbd "p")) + "List of registered bookmarks.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun bookmark-open (b) + "Open bookmark, B, in a new buffer or an ivy minibuffer." + (let ((path (bookmark-path b))) + (cond + ((f-directory? path) + (funcall bookmark-handle-directory path)) + ((f-file? path) + (funcall bookmark-handle-file path))))) + + +(defun bookmark-install-kbds () + "Install the keybindings defined herein." + (->> bookmark-whitelist + (list-map + (lambda (b) + (general-define-key + :prefix "" + :states '(normal) + (format "J%s" (bookmark-kbd b)) + (lambda () (interactive) (find-file (bookmark-path b))) + (format "j%s" (bookmark-kbd b)) + ;; TODO: Consider `cl-labels' so `which-key' minibuffer is more + ;; helpful. + (lambda () (interactive) (bookmark-open b))))))) + +(provide 'bookmark) +;;; bookmark.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/buffer.el b/users/wpcarro/emacs/.emacs.d/wpc/buffer.el new file mode 100644 index 000000000..3c78601b7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/buffer.el @@ -0,0 +1,206 @@ +;;; buffer.el --- Working with buffers -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Utilities for CRUDing buffers in Emacs. +;; +;; Many of these functions may seem unnecessary especially when you consider +;; there implementations. In general I believe that Elisp suffers from a +;; library disorganization problem. Providing simple wrapper functions that +;; rename functions or reorder parameters is worth the effort in my opinion if +;; it improves discoverability (via intuition) and improve composability. +;; +;; I support three ways for switching between what I'm calling "source code +;; buffers": +;; 1. Toggling previous: +;; 2. Using `ivy-read': b +;; TODO: These obscure evil KBDs. Maybe a hydra definition would be best? +;; 3. Cycling (forwards/backwards): C-f, C-b + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'maybe) +(require 'set) +(require 'cycle) +(require 'struct) +(require 'ts) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst buffer-enable-tests? t + "When t, run the test suite.") + +(defconst buffer-install-kbds? t + "When t, install the keybindings defined herein.") + +(defconst buffer-source-code-blacklist + (set-new 'dired-mode + 'erc-mode + 'vterm-mode + 'magit-status-mode + 'magit-process-mode + 'magit-log-mode + 'magit-diff-mode + 'org-mode + 'fundamental-mode) + "A blacklist of major-modes to ignore for listing source code buffers.") + +(defconst buffer-source-code-timeout 2 + "Number of seconds to wait before invalidating the cycle.") + +(cl-defstruct source-code-cycle cycle last-called) + +(defun buffer-emacs-generated? (name) + "Return t if buffer, NAME, is an Emacs-generated buffer. +Some buffers are Emacs-generated but are surrounded by whitespace." + (let ((trimmed (s-trim name))) + (and (s-starts-with? "*" trimmed)))) + +(defun buffer-find (buffer-or-name) + "Find a buffer by its BUFFER-OR-NAME." + (get-buffer buffer-or-name)) + +(defun buffer-major-mode (name) + "Return the active `major-mode' in buffer, NAME." + (with-current-buffer (buffer-find name) + major-mode)) + +(defun buffer-source-code-buffers () + "Return a list of source code buffers. +This will ignore Emacs-generated buffers, like *Messages*. It will also ignore + any buffer whose major mode is defined in `buffer-source-code-blacklist'." + (->> (buffer-list) + (list-map #'buffer-name) + (list-reject #'buffer-emacs-generated?) + (list-reject (lambda (name) + (set-contains? (buffer-major-mode name) + buffer-source-code-blacklist))))) + +(defvar buffer-source-code-cycle-state + (make-source-code-cycle + :cycle (cycle-from-list (buffer-source-code-buffers)) + :last-called (ts-now)) + "State used to manage cycling between source code buffers.") + +(defun buffer-exists? (name) + "Return t if buffer, NAME, exists." + (maybe-some? (buffer-find name))) + +(defun buffer-new (name) + "Return a newly created buffer NAME." + (generate-new-buffer name)) + +(defun buffer-find-or-create (name) + "Find or create buffer, NAME. +Return a reference to that buffer." + (let ((x (buffer-find name))) + (if (maybe-some? x) + x + (buffer-new name)))) + +;; TODO: Should this consume: `display-buffer' or `switch-to-buffer'? +(defun buffer-show (buffer-or-name) + "Display the BUFFER-OR-NAME, which is either a buffer reference or its name." + (display-buffer buffer-or-name)) + +;; TODO: Move this and `buffer-cycle-prev' into a separate module that +;; encapsulates all of this behavior. + +(defun buffer-cycle (cycle-fn) + "Using CYCLE-FN, move through `buffer-source-code-buffers'." + (let ((last-called (source-code-cycle-last-called + buffer-source-code-cycle-state)) + (cycle (source-code-cycle-cycle + buffer-source-code-cycle-state))) + (if (> (ts-diff (ts-now) last-called) + buffer-source-code-timeout) + (progn + (struct-set! source-code-cycle + cycle + (cycle-from-list (buffer-source-code-buffers)) + buffer-source-code-cycle-state) + (let ((cycle (source-code-cycle-cycle + buffer-source-code-cycle-state))) + (funcall cycle-fn cycle) + (switch-to-buffer (cycle-current cycle))) + (struct-set! source-code-cycle + last-called + (ts-now) + buffer-source-code-cycle-state)) + (progn + (funcall cycle-fn cycle) + (switch-to-buffer (cycle-current cycle)))))) + +(defun buffer-cycle-next () + "Cycle forward through the `buffer-source-code-buffers'." + (interactive) + (buffer-cycle #'cycle-next)) + +(defun buffer-cycle-prev () + "Cycle backward through the `buffer-source-code-buffers'." + (interactive) + (buffer-cycle #'cycle-prev)) + +(defun buffer-ivy-source-code () + "Use `ivy-read' to choose among all open source code buffers." + (interactive) + (ivy-read "Source code buffer: " + (-drop 1 (buffer-source-code-buffers)) + :sort nil + :action #'switch-to-buffer)) + +(defun buffer-show-previous () + "Call `switch-to-buffer' on the previously visited buffer. +This function ignores Emacs-generated buffers, i.e. the ones that look like + this: *Buffer*. It also ignores buffers that are `dired-mode' or `erc-mode'. + This blacklist can easily be changed." + (interactive) + (let* ((xs (buffer-source-code-buffers)) + (candidate (list-get 1 xs))) + (prelude-assert (maybe-some? candidate)) + (switch-to-buffer candidate))) + +(when buffer-install-kbds? + (general-define-key + :states '(normal) + "C-f" #'buffer-cycle-next + "C-b" #'buffer-cycle-prev) + (general-define-key + :prefix "" + :states '(normal) + "b" #'buffer-ivy-source-code + "" #'buffer-show-previous + "k" #'kill-buffer)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when buffer-enable-tests? + (prelude-assert + (list-all? #'buffer-emacs-generated? + '("*scratch*" + "*Messages*" + "*shell*" + "*Shell Command Output*" + "*Occur*" + "*Warnings*" + "*Help*" + "*Completions*" + "*Apropos*" + "*info*")))) + +(provide 'buffer) +;;; buffer.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/bytes.el b/users/wpcarro/emacs/.emacs.d/wpc/bytes.el new file mode 100644 index 000000000..48d3932f1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/bytes.el @@ -0,0 +1,113 @@ +;;; bytes.el --- Working with byte values -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Functions to help with human-readable representations of byte values. +;; +;; Usage: +;; See the test cases for example usage. Or better yet, I should use a type of +;; structured documentation that would allow me to expose a view into the test +;; suite here. Is this currently possible in Elisp? +;; +;; API: +;; - serialize :: Integer -> String +;; +;; Wish list: +;; - Rounding: e.g. (bytes (* 1024 1.7)) => "2KB" + +;;; Code: + +;; TODO: Support -ibabyte variants like Gibibyte (GiB). + +;; Ranges: +;; B: [ 0, 1e3) +;; KB: [ 1e3, 1e6) +;; MB: [ 1e6, 1e6) +;; GB: [ 1e9, 1e12) +;; TB: [1e12, 1e15) +;; PB: [1e15, 1e18) +;; +;; Note: I'm currently not support exabytes because that causes the integer to +;; overflow. I imagine a larger integer type may exist, but for now, I'll +;; treat this as a YAGNI. + +(require 'prelude) +(require 'tuple) +(require 'math) +(require 'number) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst bytes-kb (math-exp 2 10) + "Number of bytes in a kilobyte.") + +(defconst bytes-mb (math-exp 2 20) + "Number of bytes in a megabytes.") + +(defconst bytes-gb (math-exp 2 30) + "Number of bytes in a gigabyte.") + +(defconst bytes-tb (math-exp 2 40) + "Number of bytes in a terabyte.") + +(defconst bytes-pb (math-exp 2 50) + "Number of bytes in a petabyte.") + +(defconst bytes-eb (math-exp 2 60) + "Number of bytes in an exabyte.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun bytes-classify (x) + "Return unit that closest fits byte count, X." + (prelude-assert (number-whole? x)) + (cond + ((and (>= x 0) (< x bytes-kb)) 'byte) + ((and (>= x bytes-kb) (< x bytes-mb)) 'kilobyte) + ((and (>= x bytes-mb) (< x bytes-gb)) 'megabyte) + ((and (>= x bytes-gb) (< x bytes-tb)) 'gigabyte) + ((and (>= x bytes-tb) (< x bytes-pb)) 'terabyte) + ((and (>= x bytes-pb) (< x bytes-eb)) 'petabyte))) + +(defun bytes-to-string (x) + "Convert integer X into a human-readable string." + (let ((base-and-unit + (pcase (bytes-classify x) + ('byte (tuple/from 1 "B")) + ('kilobyte (tuple/from bytes-kb "KB")) + ('megabyte (tuple/from bytes-mb "MB")) + ('gigabyte (tuple/from bytes-gb "GB")) + ('terabyte (tuple/from bytes-tb "TB")) + ('petabyte (tuple/from bytes-pb "PB"))))) + (string-format "%d%s" + (round x (tuple/first base-and-unit)) + (tuple/second base-and-unit)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (prelude-assert + (equal "1000B" (bytes-to-string 1000))) + (prelude-assert + (equal "2KB" (bytes-to-string (* 2 bytes-kb)))) + (prelude-assert + (equal "17MB" (bytes-to-string (* 17 bytes-mb)))) + (prelude-assert + (equal "419GB" (bytes-to-string (* 419 bytes-gb)))) + (prelude-assert + (equal "999TB" (bytes-to-string (* 999 bytes-tb)))) + (prelude-assert + (equal "2PB" (bytes-to-string (* 2 bytes-pb))))) + +(provide 'bytes) +;;; bytes.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/cache.el b/users/wpcarro/emacs/.emacs.d/wpc/cache.el new file mode 100644 index 000000000..be2049091 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/cache.el @@ -0,0 +1,89 @@ +;;; cache.el --- Caching things -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; An immutable cache data structure. +;; +;; This is like a sideways stack, that you can pull values out from and re-push +;; to the top. It'd be like a stack supporting push, pop, pull. +;; +;; This isn't a key-value data-structure like you might expect from a +;; traditional cache. The name is subject to change, but the underlying idea of +;; a cache remains the same. +;; +;; Think about prescient.el, which uses essentially an LRU cache integrated into +;; counsel to help create a "clairovoyant", self-organizing list. +;; +;; Use-cases: +;; - Keeps an cache of workspaces sorted as MRU with an LRU eviction strategy. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'struct) +(require '>) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct cache xs) + +;; TODO: Prefer another KBD for yasnippet form completion than company-mode's +;; current KBD. + +(defun cache-from-list (xs) + "Turn list, XS, into a cache." + (make-cache :xs xs)) + +(defun cache-contains? (x xs) + "Return t if X in XS." + (->> xs + cache-xs + (list-contains? x))) + +(defun cache-touch (x xs) + "Ensure value X in cache, XS, is front of the list. +If X isn't in XS (using `equal'), insert it at the front." + (struct-update + cache + xs + (>-> (list-reject (lambda (y) (equal x y))) + (list-cons x)) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (let ((cache (cache-from-list '("chicken" "nugget")))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; contains?/2 + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (prelude-refute + (cache-contains? "turkey" cache)) + (prelude-assert + (cache-contains? "chicken" cache)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; touch/2 + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (prelude-assert + (equal + (cache-touch "nugget" cache) + (cache-from-list '("nugget" "chicken")))) + (prelude-assert + (equal + (cache-touch "spicy" cache) + (cache-from-list '("spicy" "chicken" "nugget")))))) + +(provide 'cache) +;;; cache.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/clipboard.el b/users/wpcarro/emacs/.emacs.d/wpc/clipboard.el new file mode 100644 index 000000000..47cc45906 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/clipboard.el @@ -0,0 +1,44 @@ +;;; clipboard.el --- Working with X11's pasteboard -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Simple functions for copying and pasting. +;; +;; Integrate with bburns/clipmon so that System Clipboard can integrate with +;; Emacs's kill-ring. +;; +;; Wish list: +;; - Create an Emacs integration with github.com/cdown/clipmenud. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun clipboard-copy (x &key (message "[clipboard.el] Copied!")) + "Copy string, X, to X11's clipboard and `message' MESSAGE." + (kill-new x) + (message message)) + +(cl-defun clipboard-paste (&key (message "[clipboard.el] Pasted!")) + "Paste contents of X11 clipboard and `message' MESSAGE." + (yank) + (message message)) + +(defun clipboard-contents () + "Return the contents of the clipboard as a string." + (substring-no-properties (current-kill 0))) + +(provide 'clipboard) +;;; clipboard.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/colorscheme.el b/users/wpcarro/emacs/.emacs.d/wpc/colorscheme.el new file mode 100644 index 000000000..a02dc67c5 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/colorscheme.el @@ -0,0 +1,86 @@ +;;; colorscheme.el --- Syntax highlight and friends -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; +;; TODO: Clarify this. +;; Since I have my own definition of "theme", which couples wallpaper, font, +;; with Emacs's traditional notion of the word "theme", I'm choosing to use +;; "colorscheme" to refer to *just* the notion of syntax highlight etc. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'cycle) +(require '>) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defcustom colorscheme-whitelist + (cycle-from-list + (->> (custom-available-themes) + (list-map #'symbol-name) + (list-filter (>-> (s-starts-with? "doom-"))) + (list-map #'intern))) + "The whitelist of colorschemes through which to cycle.") + +(defun colorscheme-current () + "Return the currently enabled colorscheme." + (cycle-current colorscheme-whitelist)) + +(defun colorscheme-disable-all () + "Disable all currently enabled colorschemes." + (interactive) + (->> custom-enabled-themes + (list-map #'disable-theme))) + +(defun colorscheme-set (theme) + "Call `load-theme' with `THEME', ensuring that the line numbers are bright. +There is no hook that I'm aware of to handle this more elegantly." + (load-theme theme t) + (prelude-set-line-number-color "#da5468")) + +(defun colorscheme-whitelist-set (colorscheme) + "Focus the COLORSCHEME in the `colorscheme-whitelist' cycle." + (cycle-focus (lambda (x) (equal x colorscheme)) colorscheme-whitelist) + (colorscheme-set (colorscheme-current))) + +(defun colorscheme-ivy-select () + "Load a colorscheme using ivy." + (interactive) + (let ((theme (ivy-read "Theme: " (cycle-to-list colorscheme-whitelist)))) + (colorscheme-disable-all) + (colorscheme-set (intern theme)))) + +(cl-defun colorscheme-cycle (&key forward?) + "Cycle next if `FORWARD?' is non-nil. +Cycle prev otherwise." + (disable-theme (cycle-current colorscheme-whitelist)) + (let ((theme (if forward? + (cycle-next colorscheme-whitelist) + (cycle-prev colorscheme-whitelist)))) + (colorscheme-set theme) + (message (s-concat "Active theme: " (symbol-to-string theme))))) + +(defun colorscheme-next () + "Disable the currently active theme and load the next theme." + (interactive) + (colorscheme-cycle :forward? t)) + +(defun colorscheme-prev () + "Disable the currently active theme and load the previous theme." + (interactive) + (colorscheme-cycle :forward? nil)) + +(provide 'colorscheme) +;;; colorscheme.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/constants.el b/users/wpcarro/emacs/.emacs.d/wpc/constants.el new file mode 100644 index 000000000..ae21a089c --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/constants.el @@ -0,0 +1,55 @@ +;;; constants.el --- Constants for organizing my Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This file contains constants that are shared across my configuration. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'f) +(require 'maybe) + +(prelude-assert (f-exists? (getenv "BRIEFCASE"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst constants-ci? + (maybe-some? (getenv "CI")) + "Encoded as t when Emacs is running in CI.") + +(defconst constants-briefcase + (getenv "BRIEFCASE") + "Path to my monorepo, which various parts of my configuration rely on.") + +;; TODO: Consider merging `ui.el' and `misc.el' because those are the only +;; current consumers of these constants, and I'm unsure if the indirection that +;; globally defined constants introduces is worth it. + +(defconst constants-current-project + constants-briefcase + "Variable holding the directory for my currently active project.") + +(defconst constants-mouse-kbds + '([mouse-1] [down-mouse-1] [drag-mouse-1] [double-mouse-1] [triple-mouse-1] + [mouse-2] [down-mouse-2] [drag-mouse-2] [double-mouse-2] [triple-mouse-2] + [mouse-3] [down-mouse-3] [drag-mouse-3] [double-mouse-3] [triple-mouse-3] + [mouse-4] [down-mouse-4] [drag-mouse-4] [double-mouse-4] [triple-mouse-4] + [mouse-5] [down-mouse-5] [drag-mouse-5] [double-mouse-5] [triple-mouse-5]) + "All of the mouse-related keybindings that Emacs recognizes.") + +(defconst constants-fill-column 80 + "Variable used to set the defaults for wrapping, highlighting, etc.") + +(provide 'constants) +;;; constants.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/cycle.el b/users/wpcarro/emacs/.emacs.d/wpc/cycle.el new file mode 100644 index 000000000..5ea015930 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/cycle.el @@ -0,0 +1,224 @@ +;;; cycle.el --- Simple module for working with cycles -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Something like this may already exist, but I'm having trouble finding it, and +;; I think writing my own is a nice exercise for learning more Elisp. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'math) +(require 'maybe) +(require 'struct) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Wish list +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; - TODO: Provide immutable variant. +;; - TODO: Replace mutable consumption with immutable variant. +;; - TODO: Replace indexing with (math-mod current cycle). + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `current-index' tracks the current index +;; `xs' is the original list +(cl-defstruct cycle current-index previous-index xs) + +(defconst cycle-enable-tests? t + "When t, run the tests defined herein.") + +(defun cycle-from-list (xs) + "Create a cycle from a list of `XS'." + (if (= 0 (length xs)) + (make-cycle :current-index nil + :previous-index nil + :xs xs) + (make-cycle :current-index 0 + :previous-index nil + :xs xs))) + +(defun cycle-new (&rest xs) + "Create a cycle with XS as the values." + (cycle-from-list xs)) + +(defun cycle-to-list (xs) + "Return the list representation of a cycle, XS." + (cycle-xs xs)) + +(defun cycle--next-index<- (lo hi x) + "Return the next index in a cycle when moving downwards. +- `LO' is the lower bound. +- `HI' is the upper bound. +- `X' is the current index." + (if (< (- x 1) lo) + (- hi 1) + (- x 1))) + +(defun cycle--next-index-> (lo hi x) + "Return the next index in a cycle when moving upwards. +- `LO' is the lower bound. +- `HI' is the upper bound. +- `X' is the current index." + (if (>= (+ 1 x) hi) + lo + (+ 1 x))) + +(defun cycle-previous-focus (cycle) + "Return the previously focused entry in CYCLE." + (let ((i (cycle-previous-index cycle))) + (if (maybe-some? i) + (nth i (cycle-xs cycle)) + nil))) + +;; TODO: Consider adding "!" to the function name herein since many of them +;; mutate the collection, and the APIs are beginning to confuse me. +(defun cycle-focus-previous! (xs) + "Jump to the item in XS that was most recently focused; return the cycle. +This will error when previous-index is nil. This function mutates the +underlying struct." + (let ((i (cycle-previous-index xs))) + (if (maybe-some? i) + (progn + (cycle-jump i xs) + (cycle-current xs)) + (error "Cannot focus the previous element since cycle-previous-index is nil")))) + +(defun cycle-next (xs) + "Return the next value in `XS' and update `current-index'." + (let* ((current-index (cycle-current-index xs)) + (next-index (cycle--next-index-> 0 (cycle-count xs) current-index))) + (struct-set! cycle previous-index current-index xs) + (struct-set! cycle current-index next-index xs) + (nth next-index (cycle-xs xs)))) + +(defun cycle-prev (xs) + "Return the previous value in `XS' and update `current-index'." + (let* ((current-index (cycle-current-index xs)) + (next-index (cycle--next-index<- 0 (cycle-count xs) current-index))) + (struct-set! cycle previous-index current-index xs) + (struct-set! cycle current-index next-index xs) + (nth next-index (cycle-xs xs)))) + +(defun cycle-current (cycle) + "Return the current value in `CYCLE'." + (nth (cycle-current-index cycle) (cycle-xs cycle))) + +(defun cycle-count (cycle) + "Return the length of `xs' in `CYCLE'." + (length (cycle-xs cycle))) + +(defun cycle-jump (i xs) + "Jump to the I index of XS." + (let ((current-index (cycle-current-index xs)) + (next-index (math-mod i (cycle-count xs)))) + (struct-set! cycle previous-index current-index xs) + (struct-set! cycle current-index next-index xs)) + xs) + +(defun cycle-focus (p cycle) + "Focus the element in CYCLE for which predicate, P, is t." + (let ((i (->> cycle + cycle-xs + (-find-index p)))) + (if i + (cycle-jump i cycle) + (error "No element in cycle matches predicate")))) + +(defun cycle-focus-item (x xs) + "Focus item, X, in cycle XS. +ITEM is the first item in XS that t for `equal'." + (cycle-focus (lambda (y) (equal x y)) xs)) + +(defun cycle-contains? (x xs) + "Return t if cycle, XS, has member X." + (->> xs + cycle-xs + (list-contains? x))) + +(defun cycle-empty? (xs) + "Return t if cycle XS has no elements." + (= 0 (length (cycle-xs xs)))) + +(defun cycle-focused? (xs) + "Return t if cycle XS has a non-nil value for current-index." + (maybe-some? (cycle-current-index xs))) + +(defun cycle-append (x xs) + "Add X to the left of the focused element in XS. +If there is no currently focused item, add X to the beginning of XS." + (if (cycle-empty? xs) + (progn + (struct-set! cycle xs (list x) xs) + (struct-set! cycle current-index 0 xs) + (struct-set! cycle previous-index nil xs)) + (let ((curr-i (cycle-current-index xs)) + (prev-i (cycle-previous-index xs))) + (if curr-i + (progn + (struct-set! cycle xs (-insert-at curr-i x (cycle-xs xs)) xs) + (when (>= prev-i curr-i) (struct-set! cycle previous-index (1+ prev-i) xs)) + (when curr-i (struct-set! cycle current-index (1+ curr-i) xs))) + (progn + (struct-set! cycle xs (cons x (cycle-xs xs)) xs) + (when prev-i (struct-set! cycle previous-index (1+ prev-i) xs)))) + xs))) + +(defun cycle-remove (x xs) + "Attempt to remove X from XS. + +X is found using `equal'. + +If X is the currently focused value, after it's deleted, current-index will be + nil. If X is the previously value, after it's deleted, previous-index will be + nil." + (let ((curr-i (cycle-current-index xs)) + (prev-i (cycle-previous-index xs)) + (rm-i (-elem-index x (cycle-xs xs)))) + (struct-set! cycle xs (-remove-at rm-i (cycle-xs xs)) xs) + (when prev-i + (when (> prev-i rm-i) (struct-set! cycle previous-index (1- prev-i) xs)) + (when (= prev-i rm-i) (struct-set! cycle previous-index nil xs))) + (when curr-i + (when (> curr-i rm-i) (struct-set! cycle current-index (1- curr-i) xs)) + (when (= curr-i rm-i) (struct-set! cycle current-index nil xs))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when cycle-enable-tests? + (let ((xs (cycle-new 1 2 3))) + (prelude-assert (maybe-nil? (cycle-previous-focus xs))) + (prelude-assert (= 1 (cycle-current xs))) + (prelude-assert (= 2 (cycle-next xs))) + (prelude-assert (= 1 (cycle-previous-focus xs))) + (prelude-assert (= 1 (->> xs (cycle-jump 0) cycle-current))) + (prelude-assert (= 2 (->> xs (cycle-jump 1) cycle-current))) + (prelude-assert (= 3 (->> xs (cycle-jump 2) cycle-current))) + (prelude-assert (= 2 (cycle-previous-focus xs))) + (prelude-assert (= 2 (cycle-focus-previous! xs))) + (prelude-assert (equal '(1 4 2 3) (cycle-xs (cycle-append 4 xs)))) + (prelude-assert (equal '(1 2 3) (cycle-xs (cycle-remove 4 xs)))) + (progn + (cycle-focus-item 3 xs) + (cycle-focus-item 2 xs) + (cycle-remove 1 xs) + (prelude-assert (= 2 (cycle-current xs))) + (prelude-assert (= 3 (cycle-previous-focus xs)))))) + +(provide 'cycle) +;;; cycle.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/device.el b/users/wpcarro/emacs/.emacs.d/wpc/device.el new file mode 100644 index 000000000..0e7992fd7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/device.el @@ -0,0 +1,50 @@ +;;; device.el --- Physical device information -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Functions for querying device information. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'al) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst device-hostname->device + '(("zeno.lon.corp.google.com" . work-desktop) + ("seneca" . work-laptop)) + "Mapping hostname to a device symbol.") + +;; TODO: Should I generate these predicates? + +(defun device-classify () + "Return the device symbol for the current host or nil if not supported." + (al-get system-name device-hostname->device)) + +(defun device-work-laptop? () + "Return t if current device is work laptop." + (equal 'work-laptop + (device-classify))) + +(defun device-work-desktop? () + "Return t if current device is work desktop." + (equal 'work-desktop + (device-classify))) + +(defun device-corporate? () + "Return t if the current device is owned by my company." + (or (device-work-laptop?) (device-work-desktop?))) + +(provide 'device) +;;; device.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/display.el b/users/wpcarro/emacs/.emacs.d/wpc/display.el new file mode 100644 index 000000000..24c00e3f7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/display.el @@ -0,0 +1,138 @@ +;;; display.el --- Working with single or multiple displays -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Mostly wrappers around xrandr. +;; +;; Troubleshooting: +;; The following commands help me when I (infrequently) interact with xrandr. +;; - xrandr --listmonitors +;; - xrandr --query + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'dash) +(require 's) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defmacro display-register (name &key + output + primary + coords + size + rate + dpi + rotate) + "Macro to define constants and two functions for {en,dis}abling a display. + +NAME - the human-readable identifier for the display +OUTPUT - the xrandr identifier for the display +PRIMARY - if true, send --primary flag to xrandr +COORDS - X and Y offsets +SIZE - the pixel resolution of the display (width height) +RATE - the refresh rate +DPI - the pixel density in dots per square inch +rotate - one of {normal,left,right,inverted} + +See the man-page for xrandr for more details." + `(progn + (defconst ,(intern (format "display-%s" name)) ,output + ,(format "The xrandr identifier for %s" name)) + (defconst ,(intern (format "display-%s-args" name)) + ,(replace-regexp-in-string + "\s+" " " + (s-format "--output ${output} ${primary-flag} --auto \ + --size ${size-x}x${size-y} --rate ${rate} --dpi ${dpi} \ + --rotate ${rotate} ${pos-flag}" + #'aget + `(("output" . ,output) + ("primary-flag" . ,(if primary "--primary" "--noprimary")) + ("pos-flag" . ,(if coords + (format "--pos %dx%d" + (car coords) + (cadr coords)) + "")) + ("size-x" . ,(car size)) + ("size-y" . ,(cadr size)) + ("rate" . ,rate) + ("dpi" . ,dpi) + ("rotate" . ,rotate)))) + ,(format "The arguments we pass to xrandr for display-%s." name)) + (defconst ,(intern (format "display-%s-command" name)) + (format "xrandr %s" ,(intern (format "display-%s-args" name))) + ,(format "The command we run to configure %s" name)) + (defun ,(intern (format "display-enable-%s" name)) () + ,(format "Attempt to enable my %s monitor" name) + (interactive) + (prelude-start-process + :name ,(format "display-enable-%s" name) + :command ,(intern (format "display-%s-command" name)))) + (defun ,(intern (format "display-disable-%s" name)) () + ,(format "Attempt to disable my %s monitor." name) + (interactive) + (prelude-start-process + :name ,(format "display-disable-%s" name) + :command ,(format + "xrandr --output %s --off" + output))))) + +(defmacro display-arrangement (name &key displays) + "Create a function, display-arrange-, to enable all your DISPLAYS." + `(defun ,(intern (format "display-arrange-%s" name)) () + (interactive) + (prelude-start-process + :name ,(format "display-configure-%s" name) + :command ,(format "xrandr %s" + (->> displays + (-map (lambda (x) + (eval (intern (format "display-%s-args" x))))) + (s-join " ")))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(display-register laptop + :output "eDP1" + :primary nil + :coords (2560 1440) + :size (1920 1080) + :rate 30.0 + :dpi 144 + :rotate normal) + +(display-register 4k-horizontal + :output "DP2" + :primary t + :coords (0 0) + :size (2560 1440) + :rate 30.0 + :dpi 144 + :rotate normal) + +(display-register 4k-vertical + :output "HDMI1" + :primary nil + :coords (-1440 -560) + :size (2560 1440) + :rate 30.0 + :dpi 144 + :rotate left) + +(display-arrangement primary + :displays (laptop 4k-horizontal 4k-vertical)) + +(provide 'display) +;;; display.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/dotted.el b/users/wpcarro/emacs/.emacs.d/wpc/dotted.el new file mode 100644 index 000000000..f400affd6 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/dotted.el @@ -0,0 +1,58 @@ +;;; dotted.el --- Working with dotted pairs in Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Part of my primitives library extensions in Elisp. Contrast my primitives +;; with the wrapper extensions that I provide, which expose immutable variants +;; of data structures like an list, alist, tuple, as well as quasi-typeclasses +;; like sequence, etc. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'macros) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun dotted-new (&optional a b) + "Create a new dotted pair of A and B." + (cons a b)) + +(defun dotted-instance? (x) + "Return t if X is a dotted pair." + (let ((b (cdr x))) + (and b (atom b)))) + +(defun dotted-first (x) + "Return the first element of X." + (car x)) + +(defun dotted-second (x) + "Return the second element of X." + (cdr x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (prelude-assert + (equal '(fname . "Bob") (dotted-new 'fname "Bob"))) + (prelude-assert + (dotted-instance? '(one . two))) + (prelude-refute + (dotted-instance? '(1 2 3)))) + +(provide 'dotted) +;;; dotted.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/email.el b/users/wpcarro/emacs/.emacs.d/wpc/email.el new file mode 100644 index 000000000..ef5d7c953 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/email.el @@ -0,0 +1,77 @@ +;;; email.el --- My email settings -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Attempting to configure to `notmuch' for my personal use. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'notmuch) +(require 'list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq notmuch-saved-searches + '((:name "inbox" :query "tag:inbox" :key "i") + (:name "direct" + :query "tag:direct and tag:unread and not tag:sent" + :key "d") + (:name "action" :query "tag:action" :key "a") + (:name "review" :query "tag:review" :key "r") + (:name "waiting" :query "tag:waiting" :key "w") + (:name "broadcast" :query "tag:/broadcast\/.+/ and tag:unread" :key "b") + (:name "systems" :query "tag:/systems\/.+/ and tag:unread" :key "s") + (:name "sent" :query "tag:sent" :key "t") + (:name "drafts" :query "tag:draft" :key "D"))) + +;; Sort results from newest-to-oldest. +(setq notmuch-search-oldest-first nil) + +;; Discard noisy email signatures. +(setq notmuch-mua-cite-function #'message-cite-original-without-signature) + +;; By default, this is just '("-inbox") +(setq notmuch-archive-tags '("-inbox" "-unread" "+archive")) + +;; Show saved searches even when they're empty. +(setq notmuch-show-empty-saved-searches t) + +;; Currently the sendmail executable on my system is symlinked to msmtp. +(setq send-mail-function #'sendmail-send-it) + +;; I'm not sure if I need this or not. Copying it from tazjin@'s monorepo. +(setq notmuch-always-prompt-for-sender nil) + +;; Add the "User-Agent" header to my emails and ensure that it includes Emacs +;; and notmuch information. +(setq notmuch-mua-user-agent-function + (lambda () + (format "Emacs %s; notmuch.el %s" emacs-version notmuch-emacs-version))) + +;; I was informed that Gmail does this server-side +(setq notmuch-fcc-dirs nil) + +;; Ensure buffers are closed after sending mail. +(setq message-kill-buffer-on-exit t) + +;; Ensure sender is correctly passed to msmtp. +(setq mail-specify-envelope-from t + message-sendmail-envelope-from 'header + mail-envelope-from 'header) + +;; Assert that no two saved searches share share a KBD +(prelude-assert + (list-xs-distinct-by? (lambda (x) (plist-get x :key)) notmuch-saved-searches)) + +(provide 'email) +;;; email.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/fonts.el b/users/wpcarro/emacs/.emacs.d/wpc/fonts.el new file mode 100644 index 000000000..ad77b512d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/fonts.el @@ -0,0 +1,172 @@ +;;; fonts.el --- Font preferences -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Control my font preferences with ELisp. + +;;; Code: + +;; TODO: `defcustom' font-size. +;; TODO: `defcustom' fonts. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'cycle) +(require 'device) +(require 'maybe) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Troubleshoot why "8" appears so large on my desktop. + +;; TODO: Consider having a different font size when I'm using my 4K monitor. + +(defconst fonts-size + (pcase (device-classify) + ('work-laptop "10") + ('work-desktop "10")) + "My preferred default font-size, which is device specific.") + +(defconst fonts-size-step 10 + "The amount (%) by which to increase or decrease a font.") + +(defconst fonts-hacker-news-recommendations + '("APL385 Unicode" + "Go Mono" + "Sudo" + "Monoid" + "Input Mono Medium" ;; NOTE: Also "Input Mono Thin" is nice. + ) + "List of fonts optimized for programming I found in a HN article.") + +(defconst fonts-whitelist + (cycle-from-list + (list-concat + fonts-hacker-news-recommendations + '("JetBrainsMono" + "Mononoki Medium" + "Monospace" + "Operator Mono Light" + "Courier" + "Andale Mono" + "Source Code Pro" + "Terminus"))) + "This is a list of my preferred fonts.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: fonts and fonts-whitelist make it difficult to name functions like +;; fonts-set as a generic Emacs function vs choosing a font from the whitelist. + +(cl-defun fonts-cycle (&key forward?) + "Cycle forwards when `FORWARD?' non-nil." + (let ((font (if forward? + (cycle-next fonts-whitelist) + (cycle-prev fonts-whitelist)))) + (message (s-concat "Active font: " font)) + (fonts-set font))) + +(defun fonts-next () + "Quickly cycle through preferred fonts." + (interactive) + (fonts-cycle :forward? t)) + +(defun fonts-prev () + "Quickly cycle through preferred fonts." + (interactive) + (fonts-cycle :forward? nil)) + +(defun fonts-set (font &optional size) + "Change the font to `FONT' with option integer, SIZE, in pixels." + (if (maybe-some? size) + (set-frame-font (string-format "%s %s" font size) nil t) + (set-frame-font font nil t))) + +(defun fonts-whitelist-set (font) + "Focuses the FONT in the `fonts-whitelist' cycle. +The size of the font is determined by `fonts-size'." + (prelude-assert (cycle-contains? font fonts-whitelist)) + (cycle-focus (lambda (x) (equal x font)) fonts-whitelist) + (fonts-set (fonts-current) fonts-size)) + +(defun fonts-ivy-select () + "Select a font from an ivy prompt." + (interactive) + (fonts-whitelist-set + (ivy-read "Font: " (cycle-to-list fonts-whitelist)))) + +(defun fonts-print-current () + "Message the currently enabled font." + (interactive) + (message + (string-format "[fonts] Current font: \"%s\"" + (fonts-current)))) + +(defun fonts-current () + "Return the currently enabled font." + (cycle-current fonts-whitelist)) + +(defun fonts-increase-size () + "Increase font size." + (interactive) + (->> (face-attribute 'default :height) + (+ fonts-size-step) + (set-face-attribute 'default (selected-frame) :height))) + +(defun fonts-decrease-size () + "Decrease font size." + (interactive) + (->> (face-attribute 'default :height) + (+ (- fonts-size-step)) + (set-face-attribute 'default (selected-frame) :height))) + +(defun fonts-reset-size () + "Restore font size to its default value." + (interactive) + (fonts-whitelist-set (fonts-current))) + +(defun fonts-enable-ligatures () + "Call this function to enable ligatures." + (interactive) + (let ((alist '((33 . ".\\(?:\\(?:==\\|!!\\)\\|[!=]\\)") + (35 . ".\\(?:###\\|##\\|_(\\|[#(?[_{]\\)") ;; + (36 . ".\\(?:>\\)") + (37 . ".\\(?:\\(?:%%\\)\\|%\\)") + (38 . ".\\(?:\\(?:&&\\)\\|&\\)") + (42 . ".\\(?:\\(?:\\*\\*/\\)\\|\\(?:\\*[*/]\\)\\|[*/>]\\)") ;; + (43 . ".\\(?:\\(?:\\+\\+\\)\\|[+>]\\)") + (45 . ".\\(?:\\(?:-[>-]\\|<<\\|>>\\)\\|[<>}~-]\\)") + (46 . ".\\(?:\\(?:\\.[.<]\\)\\|[.=-]\\)") ;; + (47 . ".\\(?:\\(?:\\*\\*\\|//\\|==\\)\\|[*/=>]\\)") + (48 . ".\\(?:x[a-zA-Z]\\)") + (58 . ".\\(?:::\\|[:=]\\)") + (59 . ".\\(?:;;\\|;\\)") + (60 . ".\\(?:\\(?:!--\\)\\|\\(?:~~\\|->\\|\\$>\\|\\*>\\|\\+>\\|--\\|<[<=-]\\|=[<=>]\\||>\\)\\|[*$+~/<=>|-]\\)") + (61 . ".\\(?:\\(?:/=\\|:=\\|<<\\|=[=>]\\|>>\\)\\|[<=>~]\\)") + (62 . ".\\(?:\\(?:=>\\|>[=>-]\\)\\|[=>-]\\)") + (63 . ".\\(?:\\(\\?\\?\\)\\|[:=?]\\)") + (91 . ".\\(?:]\\)") + (92 . ".\\(?:\\(?:\\\\\\\\\\)\\|\\\\\\)") + (94 . ".\\(?:=\\)") + (119 . ".\\(?:ww\\)") + (123 . ".\\(?:-\\)") + (124 . ".\\(?:\\(?:|[=|]\\)\\|[=>|]\\)") + (126 . ".\\(?:~>\\|~~\\|[>=@~-]\\)")))) + (dolist (char-regexp alist) + (set-char-table-range composition-function-table (car char-regexp) + `([,(cdr char-regexp) 0 font-shape-gstring]))))) + +(provide 'fonts) +;;; fonts.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/fs.el b/users/wpcarro/emacs/.emacs.d/wpc/fs.el new file mode 100644 index 000000000..2f83019fd --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/fs.el @@ -0,0 +1,70 @@ +;;; fs.el --- Make working with the filesystem easier -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.1")) + +;;; Commentary: +;; Ergonomic alternatives for working with the filesystem. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'f) +(require 's) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun fs-ensure-file (path) + "Ensure that a file and its directories in `PATH' exist. +Will error for inputs with a trailing slash." + (when (s-ends-with? "/" path) + (error (format "Input path has trailing slash: %s" path))) + (->> path + f-dirname + fs-ensure-dir) + (f-touch path)) + +(f-dirname "/tmp/a/b/file.txt") + +(defun fs-ensure-dir (path) + "Ensure that a directory and its ancestor directories in `PATH' exist." + (->> path + f-split + (apply #'f-mkdir))) + +(defun fs-ls (dir &optional full-path?) + "List the files in `DIR' one-level deep. +Should behave similarly in spirit to the Unix command, ls. +If `FULL-PATH?' is set, return the full-path of the files." + (-drop 2 (directory-files dir full-path?))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(ert-deftest fs-test-ensure-file () + (let ((file "/tmp/file/a/b/c/file.txt")) + ;; Ensure this file doesn't exist first to prevent false-positives. + (f-delete file t) + (fs-ensure-file file) + (should (and (f-exists? file) + (f-file? file))))) + +(ert-deftest fs-test-ensure-dir () + (let ((dir "/tmp/dir/a/b/c")) + ;; Ensure the directory doesn't exist. + (f-delete dir t) + (fs-ensure-dir dir) + (should (and (f-exists? dir) + (f-dir? dir))))) + +(provide 'fs) +;;; fs.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/functions.el b/users/wpcarro/emacs/.emacs.d/wpc/functions.el new file mode 100644 index 000000000..c0b873aaf --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/functions.el @@ -0,0 +1,47 @@ +;;; functions.el --- Helper functions -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This file hopefully contains friendly APIs that making ELisp development more +;; enjoyable. + +;; TODO: Break these out into separate modules. + +;;; Code: +(defun functions-evil-window-vsplit-right () + "Split the window vertically and focus the right half." + (interactive) + (evil-window-vsplit) + (windmove-right)) + +(defun functions-evil-window-split-down () + "Split the window horizontal and focus the bottom half." + (interactive) + (evil-window-split) + (windmove-down)) + +(defun functions-create-snippet () + "Create a window split and then opens the Yasnippet editor." + (interactive) + (evil-window-vsplit) + (call-interactively #'yas-new-snippet)) + +(defun functions-evil-replace-under-point () + "Faster than typing %s//thing/g." + (interactive) + (let ((term (s-replace "/" "\\/" (symbol-to-string (symbol-at-point))))) + (save-excursion + (evil-ex (concat "%s/\\b" term "\\b/"))))) + +(defun functions-buffer-dirname () + "Return the directory name of the current buffer as a string." + (->> buffer-file-name + f-dirname + f-filename)) + +(provide 'functions) +;;; functions.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/graph.el b/users/wpcarro/emacs/.emacs.d/wpc/graph.el new file mode 100644 index 000000000..9965622bb --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/graph.el @@ -0,0 +1,95 @@ +;;; graph.el --- Working with in-memory graphs -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; +;; Remember that there are optimal three ways to model a graph: +;; 1. Edge List +;; 2. Vertex Table (a.k.a. Neighbors Table) +;; 3. Adjacency Matrix +;; +;; I may call these "Edges", "Neighbors", "Adjacencies" to avoid verbose naming. +;; For now, I'm avoiding dealing with Adjacency Matrices as I don't have an +;; immediate use-case for them. This is subject to change. +;; +;; There are also hybrid representations of graphs that combine the three +;; aforementioned models. I believe Erlang's digraph module models graphs in +;; Erlang Term Storage (i.e. ETS) this way. +;; TODO: Verify this claim. +;; +;; Graphs can be weighted or unweighted. They can also be directed or +;; undirected. +;; TODO: Create a table explaining all graph variants. +;; +;; TODO: Figure out the relationship of this module and tree.el, which should in +;; principle overlap. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; For now, I'll support storing *either* neighbors or edges in the graph struct +;; as long as both aren't set, since that introduces consistency issues. I may +;; want to handle that use-case in the future, but not now. +(cl-defstruct graph neighbors edges) + +;; TODO: How do you find the starting point for a topo sort? +(defun graph-sort (xs) + "Return a topological sort of XS.") + +(defun graph-from-edges (xs) + "Create a graph struct from the Edge List, XS. +The user must pass in a valid Edge List since asserting on the shape of XS might + be expensive." + (make-graph :edges xs)) + +(defun graph-from-neighbors (xs) + "Create a graph struct from a Neighbors Table, XS. +The user must pass in a valid Neighbors Table since asserting on the shape of + XS might be expensive." + (make-graph :neighbors xs)) + +(defun graph-instance? (xs) + "Return t if XS is a graph struct." + (graph-p xs)) + +;; TODO: Model each of the mapping functions into an isomorphism. +(defun graph-edges->neighbors (xs) + "Map Edge List, XS, into a Neighbors Table." + (prelude-assert (graph-instance? xs))) + +(defun graph-neighbors->edges (xs) + "Map Neighbors Table, XS, into an Edge List." + (prelude-assert (graph-instance? xs))) + +;; Below are three different models of the same unweighted, directed graph. + +(defvar graph-edges + '((a . b) (a . c) (a . e) + (b . c) (b . d) + (c . e) + (d . f) + (e . d) (e . f))) + +(defvar graph-neighbors + ((a b c e) + (b c d) + (c e) + (d f) + (e d g) + (f))) + +(provide 'graph) +;;; graph.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/irc.el b/users/wpcarro/emacs/.emacs.d/wpc/irc.el new file mode 100644 index 000000000..f221db9eb --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/irc.el @@ -0,0 +1,171 @@ +;;; irc.el --- Configuration for IRC chat -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Need to decide which client I will use for IRC. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'erc) +(require 'cycle) +(require 'string) +(require 'prelude) +(require 'al) +(require 'set) +(require 'maybe) +(require 'macros) +(require '>) +(require 'password-store) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defcustom irc-install-kbds? t + "When t, install the keybindings defined herein.") + +(setq erc-rename-buffers t) + +;; Setting `erc-join-buffer' to 'bury prevents erc from stealing focus of the +;; current buffer when it connects to IRC servers. +(setq erc-join-buffer 'bury) + +;; TODO: Find a way to avoid putting "freenode" and "#freenode" as channels +;; here. I'm doing it because when erc first connects, it's `(buffer-name)' is +;; "freenode", so when `irc-next-channel' is called, it 404s on the +;; `cycle-contains?' call in `irc-channel->cycle" unless "freenode" is there. To +;; make matters even uglier, when `erc-join-channel' is called with "freenode" +;; as the value, it connects to the "#freenode" channel, so unless "#freenode" +;; exists in this cycle also, `irc-next-channel' breaks again. +(defconst irc-server->channels + `(("irc.freenode.net" . ,(cycle-new "freenode" "#freenode" "#nixos" "#emacs" "#pass")) + ("irc.corp.google.com" . ,(cycle-new "#drive-prod"))) + "Mapping of IRC servers to a cycle of my preferred channels.") + +;; TODO: Here is another horrible hack that should be revisted. +(setq erc-autojoin-channels-alist + (->> irc-server->channels + (al-map-values #'cycle-to-list) + (al-map-keys (>-> (s-chop-prefix "irc.") + (s-chop-suffix ".net"))))) + +;; TODO: Assert that no two servers have a channel with the same name. We need +;; this because that's the assumption that underpins the `irc-channel->server' +;; function. This will probably be an O(n^2) operation. +(prelude-assert + (set-distinct? (set-from-list + (cycle-to-list + (al-get "irc.freenode.net" + irc-server->channels))) + (set-from-list + (cycle-to-list + (al-get "irc.corp.google.com" + irc-server->channels))))) + +(defun irc-channel->server (server->channels channel) + "Using SERVER->CHANNELS, resolve an IRC server from a given CHANNEL." + (let ((result (al-find (lambda (k v) (cycle-contains? channel v)) + server->channels))) + (prelude-assert (maybe-some? result)) + result)) + +(defun irc-channel->cycle (server->channels channel) + "Using SERVER->CHANNELS, resolve an IRC's channels cycle from CHANNEL." + (al-get (irc-channel->server server->channels channel) + server->channels)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun irc-message (x) + "Print message X in a structured way." + (message (string-format "[irc.el] %s" x))) + +;; TODO: Integrate Google setup with Freenode setup. + +;; TODO: Support function or KBD for switching to an ERC buffer. + +(defun irc-kill-all-erc-processes () + "Kill all ERC buffers and processes." + (interactive) + (->> (erc-buffer-list) + (-map #'kill-buffer))) + +(defun irc-switch-to-erc-buffer () + "Switch to an ERC buffer." + (interactive) + (let ((buffers (erc-buffer-list))) + (if (list-empty? buffers) + (error "[irc.el] No ERC buffers available") + (switch-to-buffer (list-head (erc-buffer-list)))))) + +(defun irc-connect-to-freenode () + "Connect to Freenode IRC." + (interactive) + (erc-ssl :server "irc.freenode.net" + :port 6697 + :nick "wpcarro" + :password (password-store-get "programming/irc/freenode") + :full-name "William Carroll")) + +;; TODO: Handle failed connections. +(defun irc-connect-to-google () + "Connect to Google's Corp IRC using ERC." + (interactive) + (erc-ssl :server "irc.corp.google.com" + :port 6697 + :nick "wpcarro" + :full-name "William Carroll")) + +;; TODO: Prefer defining these with a less homespun solution. There is a +;; function call `erc-buffer-filter' that would be more appropriate for the +;; implementation of `irc-next-channel' and `irc-prev-channel'. +(defun irc-next-channel () + "Join the next channel for the active server." + (interactive) + (with-current-buffer (current-buffer) + (let ((cycle (irc-channel->cycle irc-server->channels (buffer-name)))) + (erc-join-channel + (cycle-next cycle)) + (irc-message + (string-format "Current IRC channel: %s" (cycle-current cycle)))))) + +(defun irc-prev-channel () + "Join the previous channel for the active server." + (interactive) + (with-current-buffer (current-buffer) + (let ((cycle (irc-channel->cycle irc-server->channels (buffer-name)))) + (erc-join-channel + (cycle-prev cycle)) + (irc-message + (string-format "Current IRC channel: %s" (cycle-current cycle)))))) + +(add-hook 'erc-mode-hook (macros-disable auto-fill-mode)) +(add-hook 'erc-mode-hook (macros-disable company-mode)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Keybindings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when irc-install-kbds? + (general-define-key + :keymaps 'erc-mode-map + "" #'irc-next-channel + "" #'irc-prev-channel)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(provide 'irc) +;;; irc.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/ivy-clipmenu.el b/users/wpcarro/emacs/.emacs.d/wpc/ivy-clipmenu.el new file mode 100644 index 000000000..9f5b7ca8a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/ivy-clipmenu.el @@ -0,0 +1,138 @@ +;;; ivy-clipmenu.el --- Emacs client for clipmenu -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Ivy integration with the clipboard manager, clipmenu. Essentially, clipmenu +;; turns your system clipboard into a list. +;; +;; To use this module, you must first install clipmenu and ensure that the +;; clipmenud daemon is running. Refer to the installation instructions at +;; github.com/cdown/clipmenu for those details. +;; +;; This module intentionally does not define any keybindings since I'd prefer +;; not to presume my users' preferences. Personally, I use EXWM as my window +;; manager, so I call `exwm-input-set-key' and map it to `ivy-clipmenu-copy'. +;; +;; Usually clipmenu integrates with rofi or dmenu. This Emacs module integrates +;; with ivy. Launch this when you want to select a clip. +;; +;; Clipmenu itself supports a variety of environment variables that allow you to +;; customize its behavior. These variables are respected herein. If you'd +;; prefer to customize clipmenu's behavior from within Emacs, refer to the +;; variables defined in this module. +;; +;; For more information: +;; - See `clipmenu --help`. +;; - Visit github.com/cdown/clipmenu. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 's) +(require 'dash) +(require 'ivy) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Variables +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup ivy-clipmenu nil + "Ivy integration for clipmenu." + :group 'ivy) + +(defcustom ivy-clipmenu-directory + (or (getenv "XDG_RUNTIME_DIR") + (getenv "TMPDIR") + "/tmp") + "Base directory for clipmenu's data." + :type 'string + :group 'ivy-clipmenu) + +(defconst ivy-clipmenu-executable-version 5 + "The major version number for the clipmenu executable.") + +(defconst ivy-clipmenu-cache-directory + (f-join ivy-clipmenu-directory + (format "clipmenu.%s.%s" + ivy-clipmenu-executable-version + (getenv "USER"))) + "Directory where the clips are stored.") + +(defconst ivy-clipmenu-cache-file-pattern + (f-join ivy-clipmenu-cache-directory "line_cache_*") + "Glob pattern matching the locations on disk for clipmenu's labels.") + +(defcustom ivy-clipmenu-history-length + (or (getenv "CM_HISTLENGTH") 25) + "Limit the number of clips in the history. +This value defaults to 25.") + +(defvar ivy-clipmenu-history nil + "History for `ivy-clipmenu-copy'.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun ivy-clipmenu-parse-content (x) + "Parse the label from the entry, X, in clipmenu's line-cache." + (->> (s-split " " x) + (-drop 1) + (s-join " "))) + +(defun ivy-clipmenu-list-clips () + "Return a list of the content of all of the clips." + (->> ivy-clipmenu-cache-file-pattern + f-glob + (-map (lambda (path) + (s-split "\n" (f-read path) t))) + -flatten + (-reject #'s-blank?) + (-sort #'string>) + (-map #'ivy-clipmenu-parse-content) + delete-dups + (-take ivy-clipmenu-history-length))) + +(defun ivy-clipmenu-checksum (content) + "Return the CRC checksum of CONTENT." + (s-trim-right + (with-temp-buffer + (call-process "/bin/bash" nil (current-buffer) nil "-c" + (format "cksum <<<'%s'" content)) + (buffer-string)))) + +(defun ivy-clipmenu-line-to-content (line) + "Map the chosen LINE from the line cache its content from disk." + (->> line + ivy-clipmenu-checksum + (f-join ivy-clipmenu-cache-directory) + f-read)) + +(defun ivy-clipmenu-do-copy (x) + "Copy string, X, to the system clipboard." + (kill-new x) + (message "[ivy-clipmenu.el] Copied!")) + +(defun ivy-clipmenu-copy () + "Use `ivy-read' to select and copy a clip. +It's recommended to bind this function to a globally available keymap." + (interactive) + (let ((ivy-sort-functions-alist nil)) + (ivy-read "Clipmenu: " + (ivy-clipmenu-list-clips) + :history 'ivy-clipmenu-history + :action (lambda (line) + (->> line + ivy-clipmenu-line-to-content + ivy-clipmenu-do-copy))))) + +(provide 'ivy-clipmenu) +;;; ivy-clipmenu.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/ivy-helpers.el b/users/wpcarro/emacs/.emacs.d/wpc/ivy-helpers.el new file mode 100644 index 000000000..d13b99353 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/ivy-helpers.el @@ -0,0 +1,68 @@ +;;; ivy-helpers.el --- More interfaces to ivy -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Hopefully to improve my workflows. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'tuple) +(require 'string) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun ivy-helpers-kv (prompt kv f) + "PROMPT users with the keys in KV and return its corresponding value. + +Apply key and value from KV to F." + (ivy-read + prompt + kv + :require-match t + :action (lambda (entry) + (funcall f (car entry) (cdr entry))))) + +(defun ivy-helpers-do-run-external-command (cmd) + "Execute the specified CMD and notify the user when it finishes." + (message "Starting %s..." cmd) + (set-process-sentinel + (start-process-shell-command cmd nil cmd) + (lambda (process event) + (when (string= event "finished\n") + (message "%s process finished." process))))) + +(defun ivy-helpers-list-external-commands () + "Create a list of all external commands available on $PATH." + (cl-loop + for dir in (split-string (getenv "PATH") path-separator) + when (and (file-exists-p dir) (file-accessible-directory-p dir)) + for lsdir = (cl-loop for i in (directory-files dir t) + for bn = (file-name-nondirectory i) + when (and (not (s-contains? "-wrapped" i)) + (not (member bn completions)) + (not (file-directory-p i)) + (file-executable-p i)) + collect bn) + append lsdir into completions + finally return (sort completions 'string-lessp))) + +(defun ivy-helpers-run-external-command () + "Prompts the user with a list of all installed applications to launch." + (interactive) + (let ((external-commands-list (ivy-helpers-list-external-commands))) + (ivy-read "Command:" external-commands-list + :require-match t + :action #'ivy-helpers-do-run-external-command))) + +;;; Code: +(provide 'ivy-helpers) +;;; ivy-helpers.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/kbd.el b/users/wpcarro/emacs/.emacs.d/wpc/kbd.el new file mode 100644 index 000000000..b456f30cb --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/kbd.el @@ -0,0 +1,86 @@ +;;; kbd.el --- Elisp keybinding -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; In order to stay organized, I'm attempting to dedicate KBD prefixes to +;; specific functions. I'm hoping I can be more deliberate with my keybinding +;; choices this way. +;; +;; Terminology: +;; For a more thorough overview of the terminology refer to `keybindings.md' +;; file. Here's a brief overview: +;; - workspace: Anything concerning EXWM workspaces. +;; - x11: Anything concerning X11 applications. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'al) +(require 'set) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst kbd-prefixes + '((workspace . "s") + (x11 . "C-s")) + "Mapping of functions to designated keybinding prefixes to stay organized.") + +;; Assert that no keybindings are colliding. +(prelude-assert + (= (al-count kbd-prefixes) + (->> kbd-prefixes + al-values + set-from-list + set-count))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun kbd-raw (f x) + "Return the string keybinding for function F and appendage X. +Values for F include: +- workspace +- x11" + (prelude-assert (al-has-key? f kbd-prefixes)) + (string-format + "%s-%s" + (al-get f kbd-prefixes) + x)) + +(defun kbd-for (f x) + "Return the `kbd' for function F and appendage X. +Values for F include: +- workspace +- x11" + (kbd (kbd-raw f x))) + +;; TODO: Prefer copying human-readable versions to the clipboard. Right now +;; this isn't too useful. +(defun kbd-copy-keycode () + "Copy the pressed key to the system clipboard." + (interactive) + (message "[kbd] Awaiting keypress...") + (let ((key (read-key))) + (clipboard-copy (string-format "%s" key)) + (message (string-format "[kbd] \"%s\" copied!" key)))) + +(defun kbd-print-keycode () + "Prints the pressed keybinding." + (interactive) + (message "[kbd] Awaiting keypress...") + (message (string-format "[kbd] keycode: %s" (read-key)))) + +(provide 'kbd) +;;; kbd.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/keybindings.el b/users/wpcarro/emacs/.emacs.d/wpc/keybindings.el new file mode 100644 index 000000000..bbc7aaa47 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/keybindings.el @@ -0,0 +1,367 @@ +;;; keybindings.el --- Centralizing my keybindings -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Attempting to centralize my keybindings to simplify my configuration. +;; +;; I have some expectations about my keybindings. Here are some of those +;; defined: +;; - In insert mode: +;; - C-a: beginning-of-line +;; - C-e: end-of-line +;; - C-b: backwards-char +;; - C-f: forwards-char + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'functions) +(require 'clipboard) +(require 'screen-brightness) +(require 'pulse-audio) +(require 'scrot) +(require 'ivy-clipmenu) +(require 'general) +(require 'exwm) +(require 'vterm-mgt) +(require 'buffer) +(require 'display) +(require 'device) +(require 'fonts) +(require 'bookmark) +(require 'constants) +(require 'window-manager) + +;; Note: The following lines must be sorted this way. +(setq evil-want-integration t) +(setq evil-want-keybinding nil) +(general-evil-setup) +(require 'evil) +(require 'evil-collection) +(require 'evil-magit) +(require 'evil-commentary) +(require 'evil-surround) +(require 'key-chord) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; General Keybindings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Install KBDs like jb to search through my monorepo. +(bookmark-install-kbds) + +;; Ensure that evil's command mode behaves with readline bindings. +(general-define-key + :keymaps 'evil-ex-completion-map + "C-a" #'move-beginning-of-line + "C-e" #'move-end-of-line + "C-k" #'kill-line + "C-u" #'evil-delete-whole-line + "C-v" #'evil-paste-after + "C-d" #'delete-char + "C-f" #'forward-char + "M-b" #'backward-word + "M-f" #'forward-word + "M-d" #'kill-word + "M-DEL" #'backward-kill-word + "C-b" #'backward-char) + +(general-mmap + :keymaps 'override + "RET" #'evil-goto-line + "H" #'evil-first-non-blank + "L" #'evil-end-of-line + "_" #'ranger + "-" #'dired-jump + "sl" #'functions-evil-window-vsplit-right + "sh" #'evil-window-vsplit + "sk" #'evil-window-split + "sj" #'functions-evil-window-split-down) + +(general-nmap + :keymaps 'override + "gu" #'browse-url-at-point + "gd" #'xref-find-definitions + ;; Wrapping `xref-find-references' in the `let' binding to prevent xref from + ;; prompting. There are other ways to handle this variable, such as setting + ;; it globally with `setq' or buffer-locally with `setq-local'. For now, I + ;; prefer setting it with `let', which should bind it in the dynamic scope + ;; for the duration of the `xref-find-references' function call. + "gx" (lambda () + (interactive) + (let ((xref-prompt-for-identifier nil)) + (call-interactively #'xref-find-references)))) + +(general-unbind 'motion "M-." "C-p" "") +(general-unbind 'normal "s" "M-." "C-p" "C-n") +(general-unbind 'insert "C-v" "C-d" "C-a" "C-e" "C-n" "C-p" "C-k") + +(setq evil-symbol-word-search t) +(evil-mode 1) +(evil-collection-init) +(evil-commentary-mode) +(global-evil-surround-mode 1) + +;; Ensure the Evil search results get centered vertically. +;; When Emacs is run from a terminal, this forces Emacs to redraw itself, which +;; is visually disruptive. +(when window-system + (progn + (defadvice isearch-update + (before advice-for-isearch-update activate) + (evil-scroll-line-to-center (line-number-at-pos))) + (defadvice evil-search-next + (after advice-for-evil-search-next activate) + (evil-scroll-line-to-center (line-number-at-pos))) + (defadvice evil-search-previous + (after advice-for-evil-search-previous activate) + (evil-scroll-line-to-center (line-number-at-pos))))) + +(key-chord-mode 1) +(key-chord-define evil-insert-state-map "jk" 'evil-normal-state) + +;; This may be contraversial, but I never use the prefix key, and I'd prefer to +;; have to bound to the readline function that deletes the entire line. +(general-unbind "C-u") + +(defmacro keybindings-exwm (c fn) + "Bind C to FN using `exwm-input-set-key' with `kbd' applied to C." + `(exwm-input-set-key (kbd ,c) ,fn)) + +(keybindings-exwm "C-M-v" #'ivy-clipmenu-copy) +(keybindings-exwm "" #'screen-brightness-increase) +(keybindings-exwm "" #'screen-brightness-decrease) +(keybindings-exwm "" #'pulse-audio-toggle-mute) +(keybindings-exwm "" #'pulse-audio-decrease-volume) +(keybindings-exwm "" #'pulse-audio-increase-volume) +(keybindings-exwm "" #'pulse-audio-toggle-microphone) +(keybindings-exwm (kbd-raw 'x11 "s") #'scrot-select) +(keybindings-exwm "" #'window-manager-switch-to-exwm-buffer) +(keybindings-exwm (kbd-raw 'workspace "k") #'fonts-increase-size) +(keybindings-exwm (kbd-raw 'workspace "j") #'fonts-decrease-size) +(keybindings-exwm (kbd-raw 'workspace "0") #'fonts-reset-size) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Window sizing +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm "C-M-=" #'balance-windows) +(keybindings-exwm "C-M-j" #'shrink-window) +(keybindings-exwm "C-M-k" #'enlarge-window) +(keybindings-exwm "C-M-h" #'shrink-window-horizontally) +(keybindings-exwm "C-M-l" #'enlarge-window-horizontally) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Window Management +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm "M-h" #'windmove-left) +(keybindings-exwm "M-j" #'windmove-down) +(keybindings-exwm "M-k" #'windmove-up) +(keybindings-exwm "M-l" #'windmove-right) +(keybindings-exwm "M-\\" #'evil-window-vsplit) +(keybindings-exwm "M--" #'evil-window-split) +(keybindings-exwm "M-q" #'delete-window) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm "M-:" #'eval-expression) +(keybindings-exwm "M-SPC" #'ivy-helpers-run-external-command) +(keybindings-exwm "M-x" #'counsel-M-x) +(keybindings-exwm "" #'window-manager-next-workspace) +(keybindings-exwm "" #'window-manager-prev-workspace) +(keybindings-exwm "C-M-\\" #'ivy-pass) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Workspaces +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm (kbd-raw 'workspace "l") #'window-manager-logout) + +(general-define-key + :keymaps 'override + "M-q" #'delete-window + "" #'toggle-frame-fullscreen + "M-h" #'windmove-left + "M-l" #'windmove-right + "M-k" #'windmove-up + "M-j" #'windmove-down + "M-q" #'delete-window) + +;; Support pasting in M-:. +(general-define-key + :keymaps 'read-expression-map + "C-v" #'clipboard-yank + "C-S-v" #'clipboard-yank) + +(general-define-key + :prefix "" + :states '(normal) + "." #'ffap + "gn" #'notmuch + "i" #'counsel-semantic-or-imenu + "I" #'ibuffer + "hk" #'helpful-callable + "hf" #'helpful-function + "hm" #'helpful-macro + "hc" #'helpful-command + "hk" #'helpful-key + "hv" #'helpful-variable + "hp" #'helpful-at-point + "hi" #'info-apropos + "s" #'flyspell-mode + "S" #'sort-lines + "=" #'align + "p" #'flycheck-previous-error + "f" #'project-find-file + "n" #'flycheck-next-error + "N" #'smerge-next + "W" #'balance-windows + "gss" #'magit-status + "gsb" (lambda () + (interactive) + (magit-status constants-briefcase)) + "E" #'refine + "es" #'functions-create-snippet + "l" #'linum-mode + "B" #'magit-blame + "w" #'save-buffer + "r" #'functions-evil-replace-under-point + "R" #'deadgrep) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Vterm +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Show or hide a vterm buffer. I'm intentionally not defining this in +;; vterm-mgt.el because it consumes `buffer-show-previous', and I'd like to +;; avoid bloating vterm-mgt.el with dependencies that others may not want. +(general-define-key (kbd-raw 'x11 "t") + (lambda () + (interactive) + (if (vterm-mgt--instance? (current-buffer)) + (switch-to-buffer (first (buffer-source-code-buffers))) + (call-interactively #'vterm-mgt-find-or-create)))) + +(general-define-key + :keymaps '(vterm-mode-map) + "C-S-n" #'vterm-mgt-instantiate + "C-S-w" #'vterm-mgt-kill + "" #'vterm-mgt-next + "" #'vterm-mgt-prev + "" #'vterm-mgt-rename-buffer) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Displays +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when (device-work-laptop?) + (general-define-key + :prefix "" + :states '(normal) + "d0" #'display-enable-laptop + "D0" #'display-disable-laptop + "d1" #'display-enable-4k-horizontal + "D1" #'display-disable-4k-horizontal)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; notmuch +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; evil-collection adds many KBDs to notmuch modes. Some of these I find +;; disruptive. +(general-define-key + :states '(normal) + :keymaps '(notmuch-show-mode-map) + "M-j" nil + "M-k" nil + "" #'notmuch-show-previous-thread-show + "" #'notmuch-show-next-thread-show + "e" #'notmuch-show-archive-message-then-next-or-next-thread) + +;; TODO(wpcarro): Consider moving this to a separate module +(defun keybindings--evil-ex-define-cmd-local (cmd f) + "Define CMD to F locally to a buffer." + (unless (local-variable-p 'evil-ex-commands) + (setq-local evil-ex-commands (copy-alist evil-ex-commands))) + (evil-ex-define-cmd cmd f)) + +;; TODO(wpcarro): Support a macro that can easily define evil-ex commands for a +;; particular mode. +;; Consumption: +;; (evil-ex-for-mode 'notmuch-message-mode +;; "x" #'notmuch-mua-send-and-exit) + +(add-hook 'notmuch-message-mode-hook + (lambda () + (keybindings--evil-ex-define-cmd-local "x" #'notmuch-mua-send-and-exit))) + +;; For now, I'm mimmicking Gmail KBDs that I have memorized and enjoy +(general-define-key + :states '(normal visual) + :keymaps '(notmuch-search-mode-map) + "M" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "+muted"))) + "mi" (lambda () + (interactive) + (notmuch-search-tag '("+inbox" "-action" "-review" "-waiting" "-muted"))) + "ma" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "+action" "-review" "-waiting"))) + "mr" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "-action" "+review" "-waiting"))) + "mw" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "-action" "-review" "+waiting"))) + "e" #'notmuch-search-archive-thread) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; magit +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :states '(normal) + :keymaps '(magit-status-mode-map + magit-log-mode-map + magit-revision-mode-map) + "l" #'evil-forward-char + "L" #'magit-log) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Info-mode +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; NOTE: I find some of the following, existing KBDs useful: +;; M-x info-apropos +;; u Info-up +;; M-n clone-buffer +(general-define-key + :states '(normal) + :keymaps '(Info-mode-map) + "SPC" nil + "g SPC" #'Info-scroll-up + "RET" #'Info-follow-nearest-node + "" #'Info-next + "" #'Info-prev + "g l" #'Info-history-back + "g t" #'Info-toc) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ibuffer +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :states '(normal) + :keymaps '(ibuffer-mode-map) + "M-j" nil + "K" #'ibuffer-do-delete) + +(provide 'keybindings) +;;; keybindings.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/keyboard.el b/users/wpcarro/emacs/.emacs.d/wpc/keyboard.el new file mode 100644 index 000000000..d5a3f92f1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/keyboard.el @@ -0,0 +1,140 @@ +;;; keyboard.el --- Managing keyboard preferences with Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Setting key repeat and other values. +;; +;; Be wary of suspiciously round numbers. Especially those divisible by ten! + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'string) +(require 'number) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support clamping functions for repeat-{rate,delay} to ensure only valid +;; values are sent to xset. +(defcustom keyboard-repeat-rate 80 + "The number of key repeat signals sent per second.") + +(defcustom keyboard-repeat-delay 170 + "The number of milliseconds before autorepeat starts.") + +(defconst keyboard-repeat-rate-copy keyboard-repeat-rate + "Copy of `keyboard-repeat-rate' to support `keyboard-reset-key-repeat'.") + +(defconst keyboard-repeat-delay-copy keyboard-repeat-delay + "Copy of `keyboard-repeat-delay' to support `keyboard-reset-key-repeat'.") + +(defcustom keyboard-install-preferences? t + "When t, install keyboard preferences.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun keyboard-message (x) + "Message X in a structured way." + (message (string-format "[keyboard.el] %s" x))) + +(cl-defun keyboard-set-key-repeat (&key + (rate keyboard-repeat-rate) + (delay keyboard-repeat-delay)) + "Use xset to set the key-repeat RATE and DELAY." + (prelude-start-process + :name "keyboard-set-key-repeat" + :command (string-format "xset r rate %s %s" delay rate))) + +;; NOTE: Settings like this are machine-dependent. For instance I only need to +;; do this on my laptop and other devices where I don't have access to my split +;; keyboard. +;; NOTE: Running keysym Caps_Lock is not idempotent. If this is called more +;; than once, xmodmap will start to error about non-existent Caps_Lock symbol. +;; For more information see here: +;; https://unix.stackexchange.com/questions/108207/how-to-map-caps-lock-as-the-compose-key-using-xmodmap-portably-and-idempotently +(defun keyboard-swap-caps-lock-and-escape () + "Swaps the caps lock and escape keys using xmodmap." + (interactive) + ;; TODO: Ensure these work once the tokenizing in prelude-start-process works + ;; as expected. + (start-process "keyboard-swap-caps-lock-and-escape" + nil "/usr/bin/xmodmap" "-e" "remove Lock = Caps_Lock") + (start-process "keyboard-swap-caps-lock-and-escape" + nil "/usr/bin/xmodmap" "-e" "keysym Caps_Lock = Escape")) + +(defun keyboard-inc-repeat-rate () + "Increment `keyboard-repeat-rate'." + (interactive) + (setq keyboard-repeat-rate (number-inc keyboard-repeat-rate)) + (keyboard-set-key-repeat :rate keyboard-repeat-rate) + (keyboard-message + (string-format "Rate: %s" keyboard-repeat-rate))) + +(defun keyboard-dec-repeat-rate () + "Decrement `keyboard-repeat-rate'." + (interactive) + (setq keyboard-repeat-rate (number-dec keyboard-repeat-rate)) + (keyboard-set-key-repeat :rate keyboard-repeat-rate) + (keyboard-message + (string-format "Rate: %s" keyboard-repeat-rate))) + +(defun keyboard-inc-repeat-delay () + "Increment `keyboard-repeat-delay'." + (interactive) + (setq keyboard-repeat-delay (number-inc keyboard-repeat-delay)) + (keyboard-set-key-repeat :delay keyboard-repeat-delay) + (keyboard-message + (string-format "Delay: %s" keyboard-repeat-delay))) + +(defun keyboard-dec-repeat-delay () + "Decrement `keyboard-repeat-delay'." + (interactive) + (setq keyboard-repeat-delay (number-dec keyboard-repeat-delay)) + (keyboard-set-key-repeat :delay keyboard-repeat-delay) + (keyboard-message + (string-format "Delay: %s" keyboard-repeat-delay))) + +(defun keyboard-print-key-repeat () + "Print the currently set values for key repeat." + (interactive) + (keyboard-message + (string-format "Rate: %s. Delay: %s" + keyboard-repeat-rate + keyboard-repeat-delay))) + +(defun keyboard-set-preferences () + "Reset the keyboard preferences to their default values. +NOTE: This function exists because occasionally I unplug and re-plug in a + keyboard and all of the preferences that I set using xset disappear." + (interactive) + (keyboard-swap-caps-lock-and-escape) + (keyboard-set-key-repeat :rate keyboard-repeat-rate + :delay keyboard-repeat-delay) + ;; TODO: Implement this message function as a macro that pulls the current + ;; file name. + (keyboard-message "Keyboard preferences set!")) + +(defun keyboard-reset-key-repeat () + "Set key repeat rate and delay to original values." + (interactive) + (keyboard-set-key-repeat :rate keyboard-repeat-rate-copy + :delay keyboard-repeat-delay-copy) + (keyboard-message "Key repeat preferences reset.")) + +(when keyboard-install-preferences? + (keyboard-set-preferences)) + +(provide 'keyboard) +;;; keyboard.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/laptop-battery.el b/users/wpcarro/emacs/.emacs.d/wpc/laptop-battery.el new file mode 100644 index 000000000..91b2e3125 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/laptop-battery.el @@ -0,0 +1,64 @@ +;;; laptop-battery.el --- Display laptop battery information -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Some wrappers to obtain battery information. +;; +;; To troubleshoot battery consumpton look into the CLI `powertop`. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Roadmap +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support functions that work with reporting battery stats. +;; TODO: low-battery-reporting-threshold +;; TODO: charged-battery-reporting-threshold +;; TODO: Format modeline battery information. +;; TODO: Provide better time information in the modeline. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'battery) +(require 'al) +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun laptop-battery-available? () + "Return t if battery information is available." + (maybe-some? battery-status-function)) + +(defun laptop-battery-percentage () + "Return the current percentage of the battery." + (->> battery-status-function + funcall + (al-get 112))) + +(defun laptop-battery-print-percentage () + "Return the current percentage of the battery." + (interactive) + (->> (laptop-battery-percentage) + message)) + +(defun laptop-battery-display () + "Display laptop battery percentage in the modeline." + (interactive) + (display-battery-mode 1)) + +(defun laptop-battery-hide () + "Hide laptop battery percentage in the modeline." + (interactive) + (display-battery-mode -1)) + +(provide 'laptop-battery) +;;; laptop-battery.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/list.el b/users/wpcarro/emacs/.emacs.d/wpc/list.el new file mode 100644 index 000000000..cc91ac1ea --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/list.el @@ -0,0 +1,222 @@ +;;; list.el --- Functions for working with lists -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Since I prefer having the `list-' namespace, I wrote this module to wrap many +;; of the functions that are defined in the the global namespace in ELisp. I +;; sometimes forget the names of these functions, so it's nice for them to be +;; organized like this. +;; +;; Motivation: +;; Here are some examples of function names that I cannot tolerate: +;; - `car': Return the first element (i.e. "head") of a linked list +;; - `cdr': Return the tail of a linked list + +;; As are most APIs for standard libraries that I write, this is heavily +;; influenced by Elixir's standard library. +;; +;; Elixir's List library: +;; - ++/2 +;; - --/2 +;; - hd/1 +;; - tl/1 +;; - in/2 +;; - length/1 +;; +;; Similar libraries: +;; - dash.el: Functional library that mimmicks Clojure. It is consumed herein. +;; - list-utils.el: Utility library that covers things that dash.el may not +;; cover. +;; stream.el: Elisp implementation of streams, "implemented as delayed +;; evaluation of cons cells." + +;; TODO: Consider naming this file linked-list.el. + +;; TODO: Support module-like macro that auto-namespaces functions. + +;; TODO: Consider wrapping most data structures like linked-lists, +;; associative-lists, etc in a `cl-defstruct', so that the dispatching by type +;; can be nominal instead of duck-typing. I'm not sure if this is a good idea +;; or not. If I do this, I should provide isomorphisms to map between idiomatic +;; ways of working with Elisp data structures and my wrapped variants. + +;; TODO: Are function aliases/synonyms even a good idea? Or do they just +;; bloat the API unnecessarily? + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Move `prelude-assert' elsewhere so that I can require it without +;; introducing the circular dependency of list.el -> prelude.el -> list.el. +;;(require 'prelude) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst list-tests? t + "When t, run the test suite.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun list-new () + "Return a new, empty list." + '()) + +(defun list-concat (&rest lists) + "Joins `LISTS' into on list." + (apply #'-concat lists)) + +(defun list-join (joint xs) + "Join a list of strings, XS, with JOINT." + (if (list-empty? xs) + "" + (list-reduce (list-first xs) + (lambda (x acc) + (string-concat acc joint x)) + (list-tail xs)))) + +(defun list-length (xs) + "Return the number of elements in `XS'." + (length xs)) + +(defun list-get (i xs) + "Return the value in `XS' at `I', or nil." + (nth i xs)) + +(defun list-head (xs) + "Return the head of `XS'." + (car xs)) + +;; TODO: Learn how to write proper function aliases. +(defun list-first (xs) + "Alias for `list-head' for `XS'." + (list-head xs)) + +(defun list-tail (xs) + "Return the tail of `XS'." + (cdr xs)) + +(defun list-reverse (xs) + "Reverses `XS'." + (reverse xs)) + +(defun list-cons (x xs) + "Add `X' to the head of `XS'." + (cons x xs)) + +;; map, filter, reduce + +;; TODO: Create function adapters like swap. +;; (defun adapter/swap (f) +;; "Return a new function that wraps `F' and swaps the arguments." +;; (lambda (a b) +;; (funcall f b a))) + +;; TODO: Make this function work. +(defun list-reduce (acc f xs) + "Return over `XS' calling `F' on an element in `XS'and `ACC'." + (-reduce-from (lambda (acc x) (funcall f x acc)) acc xs)) + +(defun list-map (f xs) + "Call `F' on each element of `XS'." + (-map f xs)) + +(defun list-map-indexed (f xs) + "Call `F' on each element of `XS' along with its index." + (-map-indexed (lambda (i x) (funcall f x i)) xs)) + +(defun list-filter (p xs) + "Return a subset of XS where predicate P returned t." + (list-reverse + (list-reduce + '() + (lambda (x acc) + (if (funcall p x) + (list-cons x acc) + acc)) + xs))) + +(defun list-reject (p xs) + "Return a subset of XS where predicate of P return nil." + (list-filter (lambda (x) (not (funcall p x))) xs)) + +(defun list-find (p xs) + "Return the first x in XS that passes P or nil." + (-find p xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun list-instance? (xs) + "Return t if `XS' is a list. +Be leery of using this with things like alists. Many data structures in Elisp + are implemented using linked lists." + (listp xs)) + +(defun list-empty? (xs) + "Return t if XS are empty." + (= 0 (list-length xs))) + +(defun list-all? (p xs) + "Return t if all `XS' pass the predicate, `P'." + (-all? p xs)) + +(defun list-any? (p xs) + "Return t if any `XS' pass the predicate, `P'." + (-any? p xs)) + +(defun list-contains? (x xs) + "Return t if X is in XS using `equal'." + (-contains? xs x)) + +(defun list-xs-distinct-by? (f xs) + "Return t if all elements in XS are distinct after applying F to each." + (= (length xs) + (->> xs (-map f) set-from-list set-count))) + +;; TODO: Support dedupe. +;; TODO: Should we call this unique? Or distinct? + +;; TODO: Add tests. +(defun list-dedupe-adjacent (xs) + "Return XS without adjacent duplicates." + (prelude-assert (not (list-empty? xs))) + (list-reduce (list (list-first xs)) + (lambda (x acc) + (if (equal x (list-first acc)) + acc + (list-cons x acc))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; (when list-tests? +;; (prelude-assert +;; (= 0 +;; (list-length '()))) +;; (prelude-assert +;; (= 5 +;; (list-length '(1 2 3 4 5)))) +;; (prelude-assert +;; (= 16 +;; (list-reduce 1 (lambda (x acc) (+ x acc)) '(1 2 3 4 5)))) +;; (prelude-assert +;; (equal '(2 4 6 8 10) +;; (list-map (lambda (x) (* x 2)) '(1 2 3 4 5))))) + +(provide 'list) +;;; list.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/macros.el b/users/wpcarro/emacs/.emacs.d/wpc/macros.el new file mode 100644 index 000000000..07b88d8f5 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/macros.el @@ -0,0 +1,64 @@ +;;; macros.el --- Helpful variables for making my ELisp life more enjoyable -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This file contains helpful variables that I use in my ELisp development. + +;; TODO: Consider a macro solution for mimmicking OCaml's auto resolution of +;; dependencies using `load-path' and friends. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'string) +(require 'symbol) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro macros-enable (mode) + "Helper for enabling `MODE'. +Useful in `add-hook' calls. Some modes, like `linum-mode' need to be called as +`(linum-mode 1)', so `(add-hook mode #'linum-mode)' won't work." + `#'(lambda nil (,mode 1))) + +(defmacro macros-disable (mode) + "Helper for disabling `MODE'. +Useful in `add-hook' calls." + `#'(lambda nil (,mode -1))) + +(defmacro macros-add-hook-before-save (mode f) + "Register a hook, `F', for a mode, `MODE' more conveniently. +Usage: (macros-add-hook-before-save 'reason-mode-hook #'refmt-before-save)" + `(add-hook ,mode + (lambda () + (add-hook 'before-save-hook ,f)))) + +;; TODO: Privatize? +(defun macros--namespace () + "Return the namespace for a function based on the filename." + (->> (buffer-file-name) + f-filename + f-base)) + +(defmacro macros-comment (&rest _) + "Empty comment s-expresion where `BODY' is ignored." + `nil) + +(defmacro macros-support-file-extension (ext mode) + "Register MODE to automatically load with files ending with EXT extension. +Usage: (macros-support-file-extension \"pb\" protobuf-mode)" + (let ((extension (string-format "\\.%s\\'" ext))) + `(add-to-list 'auto-mode-alist '(,extension . ,mode)))) + +(provide 'macros) +;;; macros.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/math.el b/users/wpcarro/emacs/.emacs.d/wpc/math.el new file mode 100644 index 000000000..9f6218fa4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/math.el @@ -0,0 +1,63 @@ +;;; math.el --- Math stuffs -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Containing some useful mathematical functions. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'maybe) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst math-pi pi + "The number pi.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support all three arguments. +;; Int -> Int -> Int -> Boolean +(cl-defun math-triangle-of-power (&key base power result) + (cond + ((maybe-somes? base power result) + (error "All three arguments should not be set")) + ((maybe-somes? power result) + (message "power and result")) + ((maybe-somes? base result) + (log result base)) + ((maybe-somes? base power) + (expt base power)) + (t + (error "Two of the three arguments must be set")))) + +(defun math-mod (x y) + "Return X mod Y." + (mod x y)) + +(defun math-exp (x y) + "Return X raised to the Y." + (expt x y)) + +(defun math-round (x) + "Round X to nearest ones digit." + (round x)) + +(defun math-floor (x) + "Floor value X." + (floor x)) + +(provide 'math) +;;; math.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/maybe.el b/users/wpcarro/emacs/.emacs.d/wpc/maybe.el new file mode 100644 index 000000000..270ee909a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/maybe.el @@ -0,0 +1,79 @@ +;;; maybe.el --- Library for dealing with nil values -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Inspired by Elm's Maybe library. +;; +;; For now, a Nothing value will be defined exclusively as a nil value. I'm +;; uninterested in supported falsiness in this module even at risk of going +;; against the LISP grain. +;; +;; I'm avoiding introducing a struct to handle the creation of Just and Nothing +;; variants of Maybe. Perhaps this is a mistake in which case this file would +;; be more aptly named nil.el. I may change that. Because of this limitation, +;; functions in Elm's Maybe library like andThen, which is the monadic bind for +;; the Maybe type, doesn't have a home here since we cannot compose multiple +;; Nothing or Just values without a struct or some other construct. +;; +;; Possible names for the variants of a Maybe. +;; None | Some +;; Nothing | Something +;; None | Just +;; Nil | Set +;; +;; NOTE: In Elisp, values like '() (i.e. the empty list) are aliases for nil. +;; What else in Elisp is an alias in this way? +;; Examples: +;; TODO: Provide examples of other nil types in Elisp. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar maybe--run-tests? t + "When t, run the test suite defined herein.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun maybe-nil? (x) + "Return t if X is nil." + (eq nil x)) + +(defun maybe-some? (x) + "Return t when X is non-nil." + (not (maybe-nil? x))) + +(defun maybe-nils? (&rest xs) + "Return t if all XS are nil." + (list-all? #'maybe-nil? xs)) + +(defun maybe-somes? (&rest xs) + "Return t if all XS are non-nil." + (list-all? #'maybe-some? xs)) + +(defun maybe-default (default x) + "Return DEFAULT when X is nil." + (if (maybe-nil? x) default x)) + +(defun maybe-map (f x) + "Apply F to X if X is not nil." + (if (maybe-some? x) + (funcall f x) + x)) + +(provide 'maybe) +;;; maybe.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/modeline.el b/users/wpcarro/emacs/.emacs.d/wpc/modeline.el new file mode 100644 index 000000000..6852bb284 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/modeline.el @@ -0,0 +1,69 @@ +;;; modeline.el --- Customize my mode-line -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Because I use EXWM, I treat my Emacs mode-line like my system bar: I need to +;; quickly check the system time, and I expect it to be at the bottom-right of +;; my Emacs frame. I used doom-modeline for awhile, which is an impressive +;; package, but it conditionally colorizes on the modeline for the active +;; buffer. So if my bottom-right window is inactive, I cannot see the time. +;; +;; My friend, @tazjin, has a modeline setup that I think is more compatible with +;; EXWM, so I'm going to base my setup off of his. + +;;; Code: + +(use-package telephone-line) + +(defun modeline-bottom-right-window? () + "Determines whether the last (i.e. +bottom-right) window of the +active frame is showing the buffer in which this function is + executed." + (let* ((frame (selected-frame)) + (right-windows (window-at-side-list frame 'right)) + (bottom-windows (window-at-side-list frame 'bottom)) + (last-window (car (seq-intersection right-windows bottom-windows)))) + (eq (current-buffer) (window-buffer last-window)))) + +(defun modeline-maybe-render-time () + "Conditionally renders the `mode-line-misc-info' string. + + The idea is to not display information like the current time, + load, battery levels on all buffers." + (when (modeline-bottom-right-window?) + (telephone-line-raw mode-line-misc-info t))) + +(defun modeline-setup () + "Render my custom modeline." + (telephone-line-defsegment telephone-line-last-window-segment () + (modeline-maybe-render-time)) + ;; Display the current EXWM workspace index in the mode-line + (telephone-line-defsegment telephone-line-exwm-workspace-index () + (when (modeline-bottom-right-window?) + (format "[%s]" exwm-workspace-current-index))) + ;; Define a highlight font for ~ important ~ information in the last + ;; window. + (defface special-highlight + '((t (:foreground "white" :background "#5f627f"))) "") + (add-to-list 'telephone-line-faces + '(highlight . (special-highlight . special-highlight))) + (setq telephone-line-lhs + '((nil . (telephone-line-position-segment)) + (accent . (telephone-line-buffer-segment)))) + (setq telephone-line-rhs + '((accent . (telephone-line-major-mode-segment)) + (nil . (telephone-line-last-window-segment + telephone-line-exwm-workspace-index)))) + (setq telephone-line-primary-left-separator 'telephone-line-tan-left + telephone-line-primary-right-separator 'telephone-line-tan-right + telephone-line-secondary-left-separator 'telephone-line-tan-hollow-left + telephone-line-secondary-right-separator 'telephone-line-tan-hollow-right) + (telephone-line-mode 1)) + +(provide 'modeline) +;;; modeline.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/number.el b/users/wpcarro/emacs/.emacs.d/wpc/number.el new file mode 100644 index 000000000..c8ed665b3 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/number.el @@ -0,0 +1,142 @@ +;;; number.el --- Functions for working with numbers -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; +;; Classifications of numbers: +;; - Natural: (a.k.a positive integers, counting numbers); {1, 2, 3, ... } +;; +;; - Whole: Natural Numbers, plus zero; {0, 1, 2, 3, ...} +;; +;; - Integers: Whole numbers plus all the negatives of the natural numbers; +;; {... , -2, -1, 0, 1, 2, ...} +;; +;; - Rational numbers: (a.k.a. fractions) where the top and bottom numbers are +;; integers; e.g., 1/2, 3/4, 7/2, ⁻4/3, 4/1. Note: The denominator cannot be +;; 0, but the numerator can be. +;; +;; - Real numbers: All numbers that can be written as a decimal. This includes +;; fractions written in decimal form e.g., 0.5, 0.75 2.35, ⁻0.073, 0.3333, or +;; 2.142857. It also includes all the irrational numbers such as π, √2 etc. +;; Every real number corresponds to a point on the number line. +;; +;; The functions defined herein attempt to capture the mathematical definitions +;; of numbers and their classifications as defined above. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst number-test? t + "When t, run the test suite defined herein.") + +;; TODO: What about int.el? + +;; TODO: How do we handle a number typeclass? + +(defun number-positive? (x) + "Return t if `X' is a positive number." + (> x 0)) + +(defun number-negative? (x) + "Return t if `X' is a positive number." + (< x 0)) + +;; TODO: Don't rely on this. Need to have 10.0 and 10 behave similarly. +(defun number-float? (x) + "Return t if `X' is a floating point number." + (floatp x)) + +(defun number-natural? (x) + "Return t if `X' is a natural number." + (and (number-positive? x) + (not (number-float? x)))) + +(defun number-whole? (x) + "Return t if `X' is a whole number." + (or (= 0 x) + (number-natural? x))) + +(defun number-integer? (x) + "Return t if `X' is an integer." + (or (number-whole? x) + (number-natural? (- x)))) + +;; TODO: How defensive should these guards be? Should we assert that the inputs +;; are integers before checking evenness or oddness? + +;; TODO: Look up Runar (from Unison) definition of handling zero as even or odd. + +;; TODO: How should rational numbers be handled? Lisp is supposedly famous for +;; its handling of rational numbers. +;; TODO: `calc-mode' supports rational numbers as "1:2" meaning "1/2" +;; (defun number-rational? (x)) + +;; TODO: Can or should I support real numbers? +;; (defun number-real? (x)) + +(defun number-even? (x) + "Return t if `X' is an even number." + (or (= 0 x) + (= 0 (mod x 2)))) + +(defun number-odd? (x) + "Return t if `X' is an odd number." + (not (number-even? x))) + +(defun number-dec (x) + "Subtract one from `X'. +While this function is undeniably trivial, I have unintentionally done (- 1 x) + when in fact I meant to do (- x 1) that I figure it's better for this function + to exist, and for me to train myself to reach for it and its inc counterpart." + (- x 1)) + +(defun number-inc (x) + "Add one to `X'." + (+ x 1)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when number-test? + (prelude-assert + (number-positive? 10)) + (prelude-assert + (number-natural? 10)) + (prelude-assert + (number-whole? 10)) + (prelude-assert + (number-whole? 0)) + (prelude-assert + (number-integer? 10)) + ;; (prelude-assert + ;; (= 120 (number-factorial 5))) + (prelude-assert + (number-even? 6)) + (prelude-refute + (number-odd? 6)) + (prelude-refute + (number-positive? -10)) + (prelude-refute + (number-natural? 10.0)) + (prelude-refute + (number-natural? -10)) + (prelude-refute + (number-natural? -10.0))) + +(provide 'number) +;;; number.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/prelude.el b/users/wpcarro/emacs/.emacs.d/wpc/prelude.el new file mode 100644 index 000000000..824a80f80 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/prelude.el @@ -0,0 +1,145 @@ +;;; prelude.el --- My attempt at augmenting Elisp stdlib -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Some of these ideas are scattered across other modules like `fs', +;; `string-functions', etc. I'd like to keep everything modular. I still don't +;; have an answer for which items belond in `misc'; I don't want that to become +;; a dumping grounds. Ideally this file will `require' all other modules and +;; define just a handful of functions. + +;; TODO: Consider removing all dependencies from prelude.el. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 's) +(require 'f) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Utilities +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun prelude-to-string (x) + "Convert X to a string." + (format "%s" x)) + +(defun prelude-inspect (&rest args) + "Message ARGS where ARGS are any type." + (->> args + (-map #'prelude-to-string) + (apply #'s-concat) + message)) + +(defmacro prelude-call-process-to-string (cmd &rest args) + "Return the string output of CMD called with ARGS." + `(with-temp-buffer + (call-process ,cmd nil (current-buffer) nil ,@args) + (buffer-string))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Assertions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Should I `throw' instead of `error' here? +(defmacro prelude-assert (x) + "Errors unless X is t. +These are strict assertions and purposely do not rely on truthiness." + (let ((as-string (prelude-to-string x))) + `(unless (equal t ,x) + (error (s-concat "Assertion failed: " ,as-string))))) + +(defmacro prelude-refute (x) + "Errors unless X is nil." + (let ((as-string (prelude-to-string x))) + `(unless (equal nil ,x) + (error (s-concat "Refutation failed: " ,as-string))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Adapter functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun prelude-identity (x) + "Return X unchanged." + x) + +(defun prelude-const (x) + "Return a variadic lambda that will return X." + (lambda (&rest _) x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Consider packaging these into a linum-color.el package. +;; TODO: Generate the color used here from the theme. +(defvar prelude--linum-safe? nil + "Flag indicating whether it is safe to work with function `linum-mode'.") + +(defvar prelude--linum-mru-color nil + "Stores the color most recently attempted to be applied.") + +(add-hook 'linum-mode-hook + (lambda () + (setq prelude--linum-safe? t) + (when (maybe-some? prelude--linum-mru-color) + (set-face-foreground 'linum prelude--linum-mru-color)))) + +(defun prelude-set-line-number-color (color) + "Safely set linum color to `COLOR'. + +If this is called before Emacs initializes, the color will be stored in +`prelude--linum-mru-color' and applied once initialization completes. + +Why is this safe? +If `(set-face-foreground 'linum)' is called before initialization completes, +Emacs will silently fail. Without this function, it is easy to introduce +difficult to troubleshoot bugs in your init files." + (if prelude--linum-safe? + (set-face-foreground 'linum color) + (setq prelude--linum-mru-color color))) + +(defun prelude-prompt (prompt) + "Read input from user with PROMPT." + (read-string prompt)) + +(cl-defun prelude-start-process (&key name command) + "Pass command string, COMMAND, and the function name, NAME. +This is a wrapper around `start-process' that has an API that resembles +`shell-command'." + ;; TODO: Fix the bug with tokenizing here, since it will split any whitespace + ;; character, even though it shouldn't in the case of quoted string in shell. + ;; e.g. - "xmodmap -e 'one two three'" => '("xmodmap" "-e" "'one two three'") + (prelude-refute (s-contains? "'" command)) + (let* ((tokens (s-split " " command)) + (program-name (nth 0 tokens)) + (program-args (cdr tokens))) + (apply #'start-process + `(,(format "*%s<%s>*" program-name name) + ,nil + ,program-name + ,@program-args)))) + +(defun prelude-executable-exists? (name) + "Return t if CLI tool NAME exists according to the variable `exec-path'." + (let ((file (locate-file name exec-path))) + (require 'maybe) + (if (maybe-some? file) + (f-exists? file) + nil))) + +(defmacro prelude-time (x) + "Print the time it takes to evaluate X." + `(benchmark 1 ',x)) + +(provide 'prelude) +;;; prelude.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/pulse-audio.el b/users/wpcarro/emacs/.emacs.d/wpc/pulse-audio.el new file mode 100644 index 000000000..d22cace46 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/pulse-audio.el @@ -0,0 +1,70 @@ +;;; pulse-audio.el --- Control audio with Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Because everything in my configuration is turning into Elisp these days. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst pulse-audio--step-size 5 + "The size by which to increase or decrease the volume.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun pulse-audio--message (x) + "Output X to *Messages*." + (message (string-format "[pulse-audio.el] %s" x))) + +(defun pulse-audio-toggle-mute () + "Mute the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-toggle-mute" + :command "pactl set-sink-mute @DEFAULT_SINK@ toggle") + (pulse-audio--message "Mute toggled.")) + +(defun pulse-audio-toggle-microphone () + "Mute the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-toggle-microphone" + :command "pactl set-source-mute @DEFAULT_SOURCE@ toggle") + (pulse-audio--message "Microphone toggled.")) + +(defun pulse-audio-decrease-volume () + "Low the volume output of the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-decrease-volume" + :command (string-format "pactl set-sink-volume @DEFAULT_SINK@ -%s%%" + pulse-audio--step-size)) + (pulse-audio--message "Volume decreased.")) + +(defun pulse-audio-increase-volume () + "Raise the volume output of the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-increase-volume" + :command (string-format "pactl set-sink-volume @DEFAULT_SINK@ +%s%%" + pulse-audio--step-size)) + (pulse-audio--message "Volume increased.")) + +(provide 'pulse-audio) +;;; pulse-audio.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/random.el b/users/wpcarro/emacs/.emacs.d/wpc/random.el new file mode 100644 index 000000000..aa3c3071b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/random.el @@ -0,0 +1,81 @@ +;;; random.el --- Functions for working with randomness -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Functions for working with randomness. Some of this code is not as +;; functional as I'd like from. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'number) +(require 'math) +(require 'series) +(require 'list) +(require 'set) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun random-int (x) + "Return a random integer from 0 to `X'." + (random x)) + +;; TODO: Make this work with sequences instead of lists. +(defun random-choice (xs) + "Return a random element of `XS'." + (let ((ct (list-length xs))) + (list-get + (random-int ct) + xs))) + +(defun random-boolean? () + "Randonly return t or nil." + (random-choice (list t nil))) + +;; TODO: This may not work if any of these generate numbers like 0, 1, etc. +(defun random-uuid () + "Return a generated UUID string." + (let ((eight (number-dec (math-triangle-of-power :base 16 :power 8))) + (four (number-dec (math-triangle-of-power :base 16 :power 4))) + (twelve (number-dec (math-triangle-of-power :base 16 :power 12)))) + (format "%x-%x-%x-%x-%x" + (random-int eight) + (random-int four) + (random-int four) + (random-int four) + (random-int twelve)))) + +(defun random-token (length) + "Return a randomly generated hexadecimal string of LENGTH." + (->> (series/range 0 (number-dec length)) + (list-map (lambda (_) (format "%x" (random-int 15)))) + (list-join ""))) + +;; TODO: Support random-sample +;; (defun random-sample (n xs) +;; "Return a randomly sample of list XS of size N." +;; (prelude-assert (and (>= n 0) (< n (list-length xs)))) +;; (cl-labels ((do-sample +;; (n xs y ys) +;; (if (= n (set-count ys)) +;; (->> ys +;; set-to-list +;; (list-map (lambda (i) +;; (list-get i xs)))) +;; (if (set-contains? y ys) +;; (do-sample n xs (random-int (list-length xs)) ys) +;; (do-sample n xs y (set-add y ys)))))) +;; (do-sample n xs (random-int (list-length xs)) (set-new)))) + +(provide 'random) +;;; random.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/region.el b/users/wpcarro/emacs/.emacs.d/wpc/region.el new file mode 100644 index 000000000..f915f24ec --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/region.el @@ -0,0 +1,24 @@ +;;; region.el --- Functions for working with regions -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Sometimes Emacs's function names and argument ordering is great; other times, +;; it isn't. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun region-to-string () + "Return the string in the active region." + (buffer-substring-no-properties (region-beginning) + (region-end))) + +(provide 'region) +;;; region.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/scope.el b/users/wpcarro/emacs/.emacs.d/wpc/scope.el new file mode 100644 index 000000000..267baac9f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/scope.el @@ -0,0 +1,107 @@ +;;; scope.el --- Work with a scope data structure -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Exposing an API for working with a scope data structure in a non-mutative +;; way. +;; +;; What's a scope? Think of a scope as a stack of key-value bindings. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'al) +(require 'stack) +(require 'struct) +(require '>) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Create +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct scope scopes) + +(defun scope-new () + "Return an empty scope." + (make-scope :scopes (->> (stack-new) + (stack-push (al-new))))) + +(defun scope-flatten (xs) + "Return a flattened representation of the scope, XS. +The newest bindings eclipse the oldest." + (->> xs + scope-scopes + stack-to-list + (list-reduce (al-new) + (lambda (scope acc) + (al-merge acc scope))))) + +(defun scope-push-new (xs) + "Push a new, empty scope onto XS." + (struct-update scope + scopes + (>-> (stack-push (al-new))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Read +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope-get (k xs) + "Return K from XS if it's in scope." + (->> xs + scope-flatten + (al-get k))) + +(defun scope-current (xs) + "Return the newest scope from XS." + (let ((xs-copy (copy-scope xs))) + (->> xs-copy + scope-scopes + stack-peek))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Update +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope-set (k v xs) + "Set value, V, at key, K, in XS for the current scope." + (struct-update scope + scopes + (>-> (stack-map-top (>-> (al-set k v)))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Delete +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope-pop (xs) + "Return a new scope without the top element from XS." + (->> xs + scope-scopes + stack-pop)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope-defined? (k xs) + "Return t if K is in scope of XS." + (->> xs + scope-flatten + (al-has-key? k))) + +;; TODO: Find a faster way to write aliases like this. +(defun scope-instance? (xs) + "Return t if XS is a scope struct." + (scope-p xs)) + +(provide 'scope) +;;; scope.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/screen-brightness.el b/users/wpcarro/emacs/.emacs.d/wpc/screen-brightness.el new file mode 100644 index 000000000..30973607b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/screen-brightness.el @@ -0,0 +1,49 @@ +;;; screen-brightness.el --- Control laptop screen brightness -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Mainly just Elisp wrappers around `xbacklight`. + +;;; Code: + +;; TODO: Define some isomorphisms. E.g. int->string, string->int. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst screen-brightness-step-size 15 + "The size of the increment or decrement step for the screen's brightness.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun screen-brightness-increase () + "Increase the screen brightness." + (interactive) + (prelude-start-process + :name "screen-brightness-increase" + :command (string-format "xbacklight -inc %s" screen-brightness-step-size)) + (message "[screen-brightness.el] Increased screen brightness.")) + +(defun screen-brightness-decrease () + "Decrease the screen brightness." + (interactive) + (prelude-start-process + :name "screen-brightness-decrease" + :command (string-format "xbacklight -dec %s" screen-brightness-step-size)) + (message "[screen-brightness.el] Decreased screen brightness.")) + +(provide 'screen-brightness) +;;; screen-brightness.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/scrot.el b/users/wpcarro/emacs/.emacs.d/wpc/scrot.el new file mode 100644 index 000000000..e7231b44f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/scrot.el @@ -0,0 +1,58 @@ +;;; scrot.el --- Screenshot functions -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; scrot is a Linux utility for taking screenshots. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'string) +(require 'ts) +(require 'clipboard) +(require 'kbd) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst scrot-screenshot-directory "~/Downloads" + "The default directory for screenshot outputs.") + +(defconst scrot-path-to-executable "/usr/bin/scrot" + "Path to the scrot executable.") + +(defconst scrot-output-format "screenshot_%H:%M:%S_%Y-%m-%d.png" + "The format string for the output screenshot file. +See scrot's man page for more information.") + +(defun scrot--copy-image (path) + "Use xclip to copy the image at PATH to the clipboard. +This currently only works for PNG files because that's what I'm outputting" + (call-process "xclip" nil nil nil + "-selection" "clipboard" "-t" "image/png" path) + (message (string-format "[scrot.el] Image copied to clipboard!"))) + +(defun scrot-select () + "Click-and-drag to screenshot a region. +The output path is copied to the user's clipboard." + (interactive) + (let ((screenshot-path (f-join scrot-screenshot-directory + (ts-format scrot-output-format (ts-now))))) + (make-process + :name "scrot-select" + :command `(,scrot-path-to-executable "--select" ,screenshot-path) + :sentinel (lambda (proc _err) + (when (= 0 (process-exit-status proc)) + (scrot--copy-image screenshot-path)))))) + +(provide 'scrot) +;;; scrot.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/sequence.el b/users/wpcarro/emacs/.emacs.d/wpc/sequence.el new file mode 100644 index 000000000..9da3ba457 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/sequence.el @@ -0,0 +1,109 @@ +;;; sequence.el --- Working with the "sequence" types -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Elisp supports a typeclass none as "sequence" which covers the following +;; types: +;; - list: '(1 2 3 4 5) +;; - vector: ["John" 27 :blue] +;; - string: "To be or not to be..." + +;; TODO: Document the difference between a "reduce" and a "fold". I.e. - reduce +;; has an initial value whereas fold uses the first element in the sequence as +;; the initial value. +;; +;; Note: This should be an approximation of Elixir's Enum protocol albeit +;; without streams. +;; +;; Elisp has done a lot of this work already and these are mostly wrapper +;; functions. +;; See the following list for reference: +;; - sequencep +;; - elt +;; - copy-sequence +;; - reverse +;; - nreverse +;; - sort +;; - seq-elt +;; - seq-length +;; - seqp +;; - seq-drop +;; - seq-take +;; - seq-take-while +;; - seq-drop-while +;; - seq-do +;; - seq-map +;; - seq-mapn +;; - seq-filter +;; - seq-remove +;; - seq-reduce +;; - seq-some +;; - seq-find +;; - seq-every-p +;; - seq-empty-p +;; - seq-count +;; - seq-sort +;; - seq-contains +;; - seq-position +;; - seq-uniq +;; - seq-subseq +;; - seq-concatenate +;; - seq-mapcat +;; - seq-partition +;; - seq-intersection +;; - seq-difference +;; - seq-group-by +;; - seq-into +;; - seq-min +;; - seq-max +;; - seq-doseq +;; - seq-let + +;;; Code: + +;; Perhaps we can provide default implementations for `filter' and `map' derived +;; from the `reduce' implementation. +;; (defprotocol sequence +;; :functions (reduce)) +;; (definstance sequence list +;; :reduce #'list-reduce +;; :filter #'list-filter +;; :map #'list-map) +;; (definstance sequence vector +;; :reduce #'vector/reduce) +;; (definstance sequence string +;; :reduce #'string) + +(defun sequence-classify (xs) + "Return the type of `XS'." + (cond + ((listp xs) 'list) + ((vectorp xs) 'vector) + ((stringp xs) 'string))) + +(defun sequence-reduce (acc f xs) + "Reduce of `XS' calling `F' on x and `ACC'." + (seq-reduce + (lambda (acc x) + (funcall f x acc)) + xs + acc)) + +;; Elixir also turned everything into a list for efficiecy reasons. + +(defun sequence-filter (p xs) + "Filter `XS' with predicate, `P'. +Returns a list regardless of the type of `XS'." + (seq-filter p xs)) + +(defun sequence-map (f xs) + "Maps `XS' calling `F' on each element. +Returns a list regardless of the type of `XS'." + (seq-map f xs)) + +(provide 'sequence) +;;; sequence.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/series.el b/users/wpcarro/emacs/.emacs.d/wpc/series.el new file mode 100644 index 000000000..00be03a97 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/series.el @@ -0,0 +1,93 @@ +;;; series.el --- Hosting common series of numbers -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Encoding number series as I learn about them. +;; +;; These are the following series I'm interested in supporting: +;; - Fibonacci +;; - Catalan numbers +;; - Figurate number series +;; - Triangular +;; - Square +;; - Pentagonal +;; - Hexagonal +;; - Lazy-caterer +;; - Magic square +;; - Look-and-say + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'number) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun series-range (beg end) + "Create a list of numbers from `BEG' to `END'. +This is an inclusive number range." + (if (< end beg) + (list-reverse + (number-sequence end beg)) + (number-sequence beg end))) + +(defun series-fibonacci-number (i) + "Return the number in the fibonacci series at `I'." + (cond + ((= 0 i) 0) + ((= 1 i) 1) + (t (+ (series-fibonacci-number (- i 1)) + (series-fibonacci-number (- i 2)))))) + +(defun series-fibonacci (n) + "Return the first `N' numbers of the fibonaccci series starting at zero." + (if (= 0 n) + '() + (list-reverse + (list-cons (series-fibonacci-number (number-dec n)) + (list-reverse + (series-fibonacci (number-dec n))))))) + +;; TODO: Consider memoization. +(defun series-triangular-number (i) + "Return the number in the triangular series at `I'." + (if (= 0 i) + 0 + (+ i (series-triangular-number (number-dec i))))) + +;; TODO: Improve performance. +;; TODO: Consider creating a stream protocol with `stream/next' and implement +;; this using that. +(defun series-triangular (n) + "Return the first `N' numbers of a triangular series starting at 0." + (if (= 0 n) + '() + (list-reverse + (list-cons (series-triangular-number (number-dec n)) + (list-reverse + (series-triangular (number-dec n))))))) + +(defun series-catalan-number (i) + "Return the catalan number in the series at `I'." + (if (= 0 i) + 1 + (/ (number-factorial (* 2 i)) + (* (number-factorial (number-inc i)) + (number-factorial i))))) + +(defun series-catalan (n) + "Return the first `N' numbers in a catalan series." + (->> (series-range 0 (number-dec n)) + (list-map #'series-catalan-number))) + +(provide 'series) +;;; series.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/set.el b/users/wpcarro/emacs/.emacs.d/wpc/set.el new file mode 100644 index 000000000..51c0c434f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/set.el @@ -0,0 +1,175 @@ +;;; set.el --- Working with mathematical sets -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; The set data structure is a collection that deduplicates its elements. + +;;; Code: + +(require 'ht) ;; friendlier API for hash-tables +(require 'dotted) +(require 'struct) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Wish List +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; - TODO: Support enum protocol for set. +;; - TODO: Prefer a different hash-table library that doesn't rely on mutative +;; code. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct set xs) + +(defconst set-enable-testing? t + "Run tests when t.") + +(defun set-from-list (xs) + "Create a new set from the list XS." + (make-set :xs (->> xs + (list-map #'dotted-new) + ht-from-alist))) + +(defun set-new (&rest args) + "Create a new set from ARGS." + (set-from-list args)) + +(defun set-to-list (xs) + "Map set XS into a list." + (->> xs + set-xs + ht-keys)) + +(defun set-add (x xs) + "Add X to set XS." + (struct-update set + xs + (lambda (table) + (let ((table-copy (ht-copy table))) + (ht-set table-copy x nil) + table-copy)) + xs)) + +;; TODO: Ensure all `*/reduce' functions share the same API. +(defun set-reduce (acc f xs) + "Return a new set by calling F on each element of XS and ACC." + (->> xs + set-to-list + (list-reduce acc f))) + +(defun set-intersection (a b) + "Return the set intersection between A and B." + (set-reduce (set-new) + (lambda (x acc) + (if (set-contains? x b) + (set-add x acc) + acc)) + a)) + +(defun set-count (xs) + "Return the number of elements in XS." + (->> xs + set-xs + ht-size)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun set-empty? (xs) + "Return t if XS has no elements in it." + (= 0 (set-count xs))) + +(defun set-contains? (x xs) + "Return t if set XS has X." + (ht-contains? (set-xs xs) x)) + +;; TODO: Prefer using `ht.el' functions for this. +(defun set-equal? (a b) + "Return t if A and B share the name members." + (ht-equal? (set-xs a) + (set-xs b))) + +(defun set-distinct? (a b) + "Return t if A and B have no shared members." + (set-empty? (set-intersection a b))) + +(defun set-superset? (a b) + "Return t if A has all of the members of B." + (->> b + set-to-list + (list-all? (lambda (x) (set-contains? x a))))) + +(defun set-subset? (a b) + "Return t if each member of set A is present in set B." + (set-superset? b a)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when set-enable-testing? + ;; set-distinct? + (prelude-assert + (set-distinct? (set-new 'one 'two 'three) + (set-new 'a 'b 'c))) + (prelude-refute + (set-distinct? (set-new 1 2 3) + (set-new 3 4 5))) + (prelude-refute + (set-distinct? (set-new 1 2 3) + (set-new 1 2 3))) + ;; set-equal? + (prelude-refute + (set-equal? (set-new 'a 'b 'c) + (set-new 'x 'y 'z))) + (prelude-refute + (set-equal? (set-new 'a 'b 'c) + (set-new 'a 'b))) + (prelude-assert + (set-equal? (set-new 'a 'b 'c) + (set-new 'a 'b 'c))) + ;; set-intersection + (prelude-assert + (set-equal? (set-new 2 3) + (set-intersection (set-new 1 2 3) + (set-new 2 3 4)))) + ;; set-{from,to}-list + (prelude-assert (equal '(1 2 3) + (->> '(1 1 2 2 3 3) + set-from-list + set-to-list))) + (let ((primary-colors (set-new "red" "green" "blue"))) + ;; set-subset? + (prelude-refute + (set-subset? (set-new "black" "grey") + primary-colors)) + (prelude-assert + (set-subset? (set-new "red") + primary-colors)) + ;; set-superset? + (prelude-refute + (set-superset? primary-colors + (set-new "black" "grey"))) + (prelude-assert + (set-superset? primary-colors + (set-new "red" "green" "blue"))) + (prelude-assert + (set-superset? primary-colors + (set-new "red" "blue")))) + ;; set-empty? + (prelude-assert (set-empty? (set-new))) + (prelude-refute (set-empty? (set-new 1 2 3))) + ;; set-count + (prelude-assert (= 0 (set-count (set-new)))) + (prelude-assert (= 2 (set-count (set-new 1 1 2 2))))) + +(provide 'set) +;;; set.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/ssh.el b/users/wpcarro/emacs/.emacs.d/wpc/ssh.el new file mode 100644 index 000000000..2e5839c04 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/ssh.el @@ -0,0 +1,65 @@ +;;; ssh.el --- When working remotely -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Configuration to make remote work easier. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'tramp) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Is "ssh" preferable to "scp"? +(setq tramp-default-method "ssh") + +;; Taken from: https://superuser.com/questions/179313/tramp-waiting-for-prompts-from-remote-shell +(setq tramp-shell-prompt-pattern "^[^$>\n]*[#$%>] *\\(\[[0-9;]*[a-zA-Z] *\\)*") + +;; Sets the value of the TERM variable to "dumb" when logging into the remote +;; host. This allows me to check for the value of "dumb" in my shell's init file +;; and control the startup accordingly. You can see in the (shamefully large) +;; commit, 0b4ef0e, that I added a check like this to my ~/.zshrc. I've since +;; switched from z-shell to fish. I don't currently have this check in +;; config.fish, but I may need to add it one day soon. +(setq tramp-terminal-type "dumb") + +;; Maximizes the tramp debugging noisiness while I'm still learning about tramp. +(setq tramp-verbose 10) + +;; As confusing as this may seem, this forces Tramp to use *my* .ssh/config +;; options, which enable ControlMaster. In other words, disabling this actually +;; enables ControlMaster. +(setq tramp-use-ssh-controlmaster-options nil) + +(defcustom ssh-hosts '("desktop" "socrates") + "List of hosts to which I commonly connect. +Note: It could be interesting to read these values from ~/.ssh-config, but + that's more than I need at the moment.") + +(defun ssh-sudo-buffer () + "Open the current buffer with sudo rights." + (interactive) + (with-current-buffer (current-buffer) + (if (s-starts-with? "/ssh:" buffer-file-name) + (message "[ssh.el] calling ssh-sudo-buffer for remote files isn't currently supported") + (find-file (format "/sudo::%s" buffer-file-name))))) + +(defun ssh-cd-home () + "Prompt for an SSH host and open a dired buffer for wpcarro on that machine." + (interactive) + (let ((machine (completing-read "Machine: " ssh-hosts))) + (find-file (format "/ssh:wpcarro@%s:~" machine)))) + +(provide 'ssh) +;;; ssh.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/stack.el b/users/wpcarro/emacs/.emacs.d/wpc/stack.el new file mode 100644 index 000000000..c90f41e76 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/stack.el @@ -0,0 +1,102 @@ +;;; stack.el --- Working with stacks in Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; A stack is a LIFO queue. +;; The design goal here is to expose an intuitive API for working with stacks in +;; non-mutative way. +;; +;; TODO: Consider naming a Functor instance "Mappable." +;; TODO: Consider naming a Foldable instance "Reduceable." +;; +;; TODO: Consider implementing an instance for Mappable. +;; TODO: Consider implementing an instance for Reduceable. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'list) +(require '>) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Create +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct stack xs) + +(defun stack-new () + "Create an empty stack." + (make-stack :xs '())) + +(defun stack-from-list (xs) + "Create a new stack from the list, `XS'." + (list-reduce (stack-new) #'stack-push xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Read +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun stack-peek (xs) + "Look at the top element of `XS' without popping it off." + (->> xs + stack-xs + list-head)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Update +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun stack-push (x xs) + "Push `X' on `XS'." + (struct-update stack + xs + (>-> (list-cons x)) + xs)) + +;; TODO: How to return something like {(list-head xs), (list-tail xs)} in Elixir +;; TODO: How to handle popping from empty stacks? +(defun stack-pop (xs) + "Return the stack, `XS', without the top element. +Since I cannot figure out a nice way of return tuples in Elisp, if you want to +look at the first element, use `stack-peek' before running `stack-pop'." + (struct-update stack + xs + (>-> list-tail) + xs)) + +(defun stack-map-top (f xs) + "Apply F to the top element of XS." + (->> xs + stack-pop + (stack-push (funcall f (stack-peek xs))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun stack-to-list (xs) + "Return XS as a list. +The round-trip property of `stack-from-list' and `stack-to-list' should hold." + (->> xs + stack-xs + list-reverse)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Create a macro that wraps `cl-defstruct' that automatically creates +;; things like `new', `instance?'. +(defun stack-instance? (xs) + "Return t if XS is a stack." + (stack-p xs)) + +(provide 'stack) +;;; stack.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/string.el b/users/wpcarro/emacs/.emacs.d/wpc/string.el new file mode 100644 index 000000000..9a43f1664 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/string.el @@ -0,0 +1,111 @@ +;;; string.el --- Library for working with strings -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Library for working with string. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 's) +(require 'dash) +;; TODO: Resolve the circular dependency that this introduces. +;; (require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun string-contains? (c x) + "Return t if X is in C." + (s-contains? c x)) + +(defun string-hookify (x) + "Append \"-hook\" to X." + (s-append "-hook" x)) + +(defun string-split (y x) + "Map string X into a list of strings that were separated by Y." + (s-split y x)) + +(defun string-ensure-hookified (x) + "Ensure that X has \"-hook\" appended to it." + (if (s-ends-with? "-hook" x) + x + (string-hookify x))) + +(defun string-format (x &rest args) + "Format template string X with ARGS." + (apply #'format (cons x args))) + +(defun string-concat (&rest strings) + "Joins `STRINGS' into onto string." + (apply #'s-concat strings)) + +(defun string-->symbol (string) + "Maps `STRING' to a symbol." + (intern string)) + +(defun string-<-symbol (symbol) + "Maps `SYMBOL' into a string." + (symbol-name symbol)) + +(defun string-prepend (prefix x) + "Prepend `PREFIX' onto `X'." + (s-concat prefix x)) + +(defun string-append (postfix x) + "Appen `POSTFIX' onto `X'." + (s-concat x postfix)) + +(defun string-surround (s x) + "Surrounds `X' one each side with `S'." + (->> x + (string-prepend s) + (string-append s))) + +;; TODO: Define a macro for defining a function and a test. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Casing +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun string-caps->kebab (x) + "Change the casing of `X' from CAP_CASE to kebab-case." + (->> x + s-downcase + (s-replace "_" "-"))) + +(defun string-kebab->caps (x) + "Change the casing of X from CAP_CASE to kebab-case." + (->> x + s-upcase + (s-replace "-" "_"))) + +(defun string-lower->caps (x) + "Change the casing of X from lowercase to CAPS_CASE." + (->> x + s-upcase + (s-replace " " "_"))) + +(defun string-lower->kebab (x) + "Change the casing of `X' from lowercase to kebab-case." + (s-replace " " "-" x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun string-instance? (x) + "Return t if X is a string." + (stringp x)) + +(provide 'string) +;;; string.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/struct.el b/users/wpcarro/emacs/.emacs.d/wpc/struct.el new file mode 100644 index 000000000..35957e834 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/struct.el @@ -0,0 +1,86 @@ +;;; struct.el --- Helpers for working with structs -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Provides new macros for working with structs. Also provides adapter +;; interfaces to existing struct macros, that should have more intuitive +;; interfaces. +;; +;; Sometimes `setf' just isn't enough. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'string) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar struct--enable-tests? t + "When t, run the test suite defined herein.") + +(defmacro struct-update (type field f xs) + "Apply F to FIELD in XS, which is a struct of TYPE. +This is immutable." + (let ((copier (->> type + symbol-name + (string-prepend "copy-") + intern)) + (accessor (->> field + symbol-name + (string-prepend (string-concat (symbol-name type) "-")) + intern))) + `(let ((copy (,copier ,xs))) + (setf (,accessor copy) (funcall ,f (,accessor copy))) + copy))) + +(defmacro struct-set (type field x xs) + "Immutably set FIELD in XS (struct TYPE) to X." + (let ((copier (->> type + symbol-name + (string-prepend "copy-") + intern)) + (accessor (->> field + symbol-name + (string-prepend (string-concat (symbol-name type) "-")) + intern))) + `(let ((copy (,copier ,xs))) + (setf (,accessor copy) ,x) + copy))) + +(defmacro struct-set! (type field x xs) + "Set FIELD in XS (struct TYPE) to X mutably. +This is an adapter interface to `setf'." + (let ((accessor (->> field + symbol-name + (string-prepend (string-concat (symbol-name type) "-")) + intern))) + `(progn + (setf (,accessor ,xs) ,x) + ,xs))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when struct--enable-tests? + (cl-defstruct dummy name age) + (defvar struct--test-dummy (make-dummy :name "Roofus" :age 19)) + (struct-set! dummy name "Doofus" struct--test-dummy) + (prelude-assert (string= "Doofus" (dummy-name struct--test-dummy))) + (let ((result (struct-set dummy name "Shoofus" struct--test-dummy))) + ;; Test the immutability of `struct-set' + (prelude-assert (string= "Doofus" (dummy-name struct--test-dummy))) + (prelude-assert (string= "Shoofus" (dummy-name result))))) + +(provide 'struct) +;;; struct.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/symbol.el b/users/wpcarro/emacs/.emacs.d/wpc/symbol.el new file mode 100644 index 000000000..39450caff --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/symbol.el @@ -0,0 +1,49 @@ +;;; symbol.el --- Library for working with symbols -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Library for working with symbols. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Symbols +(defun symbol-as-string (callback x) + "Treat the symbol, X, as a string while applying CALLBACK to it. +Coerce back to a symbol on the way out." + (->> x + #'symbol-name + callback + #'intern)) + +(defun symbol-to-string (x) + "Map `X' into a string." + (string-<-symbol x)) + +(defun symbol-hookify (x) + "Append \"-hook\" to X when X is a symbol." + (symbol-as-string #'string-hookify x)) + +(defun symbol-ensure-hookified (x) + "Ensure that X has \"-hook\" appended to it when X is a symbol." + (symbol-as-string #'string-ensure-hookified x)) + +(defun symbol-instance? (x) + "Return t if X is a symbol." + (symbolp x)) + +(provide 'symbol) +;;; symbol.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/timestring.el b/users/wpcarro/emacs/.emacs.d/wpc/timestring.el new file mode 100644 index 000000000..a9bf64e9a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/timestring.el @@ -0,0 +1,78 @@ +;;; timestring.el --- Quickly access timestamps in different formats -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: + +;; I was making some API calls where a URL needed a `since` parameter that of an +;; RFC 3339 encoded string. +;; +;; Because I didn't know what a RFC 3339 encoded +;; string was at the time, and because I didn't know what its format was +;; according to strftime, and because I'm most likely to forget both of these +;; things by the next time that I need something similar, I decided to write +;; this package so that I can accumulate a list of common time encodings. +;; +;; Thank you, Emacs. +;; +;; p.s. - I may turn this into a proper module and publish it. But not today. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ts) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup timestring nil + "Customize group for timestring configuration.") + +(defcustom timestring-supported-encodings + '(("RFC 3339" . "%Y-%m-%dT%H:%M:%SZ") + ;; Does anyone recognize this format? + ("IDK" . "%Y-%m-%d %H:%M:%S %z")) + "Mapping of encoding names to their format strings." + :group 'timestring) + +(defcustom timestring-supported-times + '(("yesterday" . timestring--yesterday) + ("now" . ts-now) + ("tomorrow" . timestring--tomorrow)) + "Mapping of a labels to the functions that create those time objects." + :group 'timestring) + +(defun timestring--yesterday () + "Return a time object for yesterday." + (ts-adjust 'day -1 (ts-now))) + +(defun timestring--tomorrow () + "Return a time object for yesterday." + (ts-adjust 'day +1 (ts-now))) + +(defun timestring--completing-read (label xs) + "Call `completing-read' with LABEL over the collection XS." + (alist-get (completing-read label xs) xs nil nil #'equal)) + +(defun timestring-copy-encoded-time () + "Select a common time and an encoding. + +The selected time will be encoded using the selected encoding and copied onto +your clipboard." + (interactive) + (let ((time (funcall (timestring--completing-read + "Time: " timestring-supported-times))) + (fmt (timestring--completing-read + "Encoding: " timestring-supported-encodings))) + (kill-new (ts-format fmt time)) + (message "Copied!"))) + +(provide 'timestring) +;;; timestring.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/tree.el b/users/wpcarro/emacs/.emacs.d/wpc/tree.el new file mode 100644 index 000000000..ae5fba795 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/tree.el @@ -0,0 +1,200 @@ +;;; tree.el --- Working with Trees -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Some friendly functions that hopefully will make working with trees cheaper +;; and therefore more appealing! +;; +;; Tree terminology: +;; - leaf: node with zero children. +;; - root: node with zero parents. +;; - depth: measures a node's distance from the root node. This implies the +;; root node has a depth of zero. +;; - height: measures the longest traversal from a node to a leaf. This implies +;; that a leaf node has a height of zero. +;; - balanced? +;; +;; Tree variants: +;; - binary: the maximum number of children is two. +;; - binary search: the maximum number of children is two and left sub-trees are +;; lower in value than right sub-trees. +;; - rose: the number of children is variable. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'list) +(require 'set) +(require 'tuple) +(require 'series) +(require 'random) +(require 'maybe) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct tree xs) + +(cl-defstruct node value children) + +(cl-defun tree-node (value &optional children) + "Create a node struct of VALUE with CHILDREN." + (make-node :value value + :children children)) + +(defun tree-reduce-breadth (acc f xs) + "Reduce over XS breadth-first applying F to each x and ACC (in that order). +Breadth-first traversals guarantee to find the shortest path in a graph. + They're typically more difficult to implement than DFTs and may also incur + higher memory costs on average than their depth-first counterparts.") + +;; TODO: Support :order as 'pre | 'in | 'post. +;; TODO: Troubleshoot why I need defensive (nil? node) check. +(defun tree-reduce-depth (acc f node) + "Reduce over NODE depth-first applying F to each NODE and ACC. +F is called with each NODE, ACC, and the current depth. +Depth-first traversals have the advantage of typically consuming less memory + than their breadth-first equivalents would have. They're also typically + easier to implement using recursion. This comes at the cost of not + guaranteeing to be able to find the shortest path in a graph." + (cl-labels ((do-reduce-depth + (acc f node depth) + (let ((acc-new (funcall f node acc depth))) + (if (or (maybe-nil? node) + (tree-leaf? node)) + acc-new + (list-reduce + acc-new + (lambda (node acc) + (tree-do-reduce-depth + acc + f + node + (number-inc depth))) + (node-children node)))))) + (do-reduce-depth acc f node 0))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Helpers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun tree-height (xs) + "Return the height of tree XS.") + +;; TODO: Troubleshoot why need for (nil? node). Similar misgiving +;; above. +(defun tree-leaf-depths (xs) + "Return a list of all of the depths of the leaf nodes in XS." + (list-reverse + (tree-reduce-depth + '() + (lambda (node acc depth) + (if (or (maybe-nil? node) + (tree-leaf? node)) + (list-cons depth acc) + acc)) + xs))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Generators +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Consider parameterizing height, forced min-max branching, random +;; distributions, etc. + +;; TODO: Bail out before stack overflowing by consider branching, current-depth. + +(cl-defun tree-random (&optional (value-fn (lambda (_) nil)) + (branching-factor 2)) + "Randomly generate a tree with BRANCHING-FACTOR. + +This uses VALUE-FN to compute the node values. VALUE-FN is called with the +current-depth of the node. Useful for generating test data. Warning this +function can overflow the stack." + (cl-labels ((do-random + (d vf bf) + (make-node + :value (funcall vf d) + :children (->> (series/range 0 (number-dec bf)) + (list-map + (lambda (_) + (when (random-boolean?) + (do-random d vf bf)))))))) + (do-random 0 value-fn branching-factor))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun tree-instance? (tree) + "Return t if TREE is a tree struct." + (node-p tree)) + +(defun tree-leaf? (node) + "Return t if NODE has no children." + (maybe-nil? (node-children node))) + +(defun tree-balanced? (n xs) + "Return t if the tree, XS, is balanced. +A tree is balanced if none of the differences between any two depths of two leaf + nodes in XS is greater than N." + (> n (->> xs + tree-leaf-depths + set-from-list + set-count + number-dec))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst tree-enable-testing? t + "When t, test suite runs.") + +;; TODO: Create set of macros for a proper test suite including: +;; - describe (arbitrarily nestable) +;; - it (arbitrarily nestable) +;; - line numbers for errors +;; - accumulated output for synopsis +;; - do we want describe *and* it? Why not a generic label that works for both? +(when tree-enable-testing? + (let ((tree-a (tree-node 1 + (list (tree-node 2 + (list (tree-node 5) + (tree-node 6))) + (tree-node 3 + (list (tree-node 7) + (tree-node 8))) + (tree-node 4 + (list (tree-node 9) + (tree-node 10)))))) + (tree-b (tree-node 1 + (list (tree-node 2 + (list (tree-node 5) + (tree-node 6))) + (tree-node 3) + (tree-node 4 + (list (tree-node 9) + (tree-node 10))))))) + ;; instance? + (prelude-assert (tree-instance? tree-a)) + (prelude-assert (tree-instance? tree-b)) + (prelude-refute (tree-instance? '(1 2 3))) + (prelude-refute (tree-instance? "oak")) + ;; balanced? + (prelude-assert (tree-balanced? 1 tree-a)) + (prelude-refute (tree-balanced? 1 tree-b)) + (message "Tests pass!"))) + +(provide 'tree) +;;; tree.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/tuple.el b/users/wpcarro/emacs/.emacs.d/wpc/tuple.el new file mode 100644 index 000000000..dd8e88f5c --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/tuple.el @@ -0,0 +1,94 @@ +;;; tuple.el --- Tuple API for Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Work with cons cells with two elements with a familiar API for those who have +;; worked with tuples before. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct tuple first second) + +;; Create +(defun tuple-new () + "Return an empty tuple." + (make-tuple :first nil + :second nil)) + +(defun tuple-from (a b) + "Return a new tuple from A and B." + (make-tuple :first a + :second b)) + +(defun tuple-from-dotted (dp) + "Convert dotted pair, DP, into a tuple." + (tuple-from (car dp) (cdr dp))) + +;; Read +(defun tuple-first (pair) + "Return the first element of PAIR." + (tuple-first pair)) + +(defun tuple-second (pair) + "Return the second element of PAIR." + (tuple-second pair)) + +;; Update +(defun tuple-map-each (f g pair) + "Apply F to first, G to second in PAIR." + (->> pair + (tuple-map-first f) + (tuple-map-second g))) + +(defun tuple-map (f pair) + "Apply F to PAIR." + (let ((pair-copy (copy-tuple pair))) + (funcall f pair-copy))) + +(defun tuple-map-first (f pair) + "Apply function F to the first element of PAIR." + (let ((pair-copy (copy-tuple pair))) + (setf (tuple-first pair-copy) (funcall f (tuple-first pair-copy))) + pair-copy)) + +(defun tuple-map-second (f pair) + "Apply function F to the second element of PAIR." + (let ((pair-copy (copy-tuple pair))) + (setf (tuple-second pair-copy) (funcall f (tuple-second pair-copy))) + pair-copy)) + +(defun tuple-set-first (a pair) + "Return a new tuple with the first element set as A in PAIR." + (tuple-map-first (lambda (_) a) pair)) + +(defun tuple-set-second (b pair) + "Return a new tuple with the second element set as B in PAIR." + (tuple-map-second (lambda (_) b) pair)) + +;; Delete +(defun tuple-delete-first (pair) + "Return PAIR with the first element set to nil." + (tuple-set-first nil pair)) + +(defun tuple-delete-second (pair) + "Return PAIR with the second element set to nil." + (tuple-set-second nil pair)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun tuple-instance? (x) + "Return t if X is a tuple." + (tuple-p x)) + +(provide 'tuple) +;;; tuple.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/vector.el b/users/wpcarro/emacs/.emacs.d/wpc/vector.el new file mode 100644 index 000000000..033f8de83 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/vector.el @@ -0,0 +1,85 @@ +;;; vector.el --- Working with Elisp's Vector data type -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; It might be best to think of Elisp vectors as tuples in languages like +;; Haskell or Erlang. +;; +;; Not surprisingly, this API is modelled after Elixir's Tuple API. +;; +;; Some Elisp trivia: +;; - "Array": Usually means vector or string. +;; - "Sequence": Usually means list or "array" (see above). +;; +;; It might be a good idea to think of Array and Sequence as typeclasses in +;; Elisp. This is perhaps more similar to Elixir's notion of the Enum protocol. +;; +;; Intentionally not supporting a to-list function, because tuples can contain +;; heterogenous types whereas lists should contain homogenous types. + +;;; Code: + +;; TODO: Consider supporting an alias named tuple for vector. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst vector-enable-tests? t + "When t, run the tests defined herein.") + +;; TODO: Consider labelling variadic functions like `vector-concat*' +;; vs. `vector-concat'. +(defun vector-concat (&rest args) + "Return a new vector composed of all vectors in `ARGS'." + (apply #'vconcat args)) + +;; TODO: Here's a sketch of a protocol macro being consumed. +;; (definstance monoid vector +;; :empty (lambda () [])) + +(defun vector-prepend (x xs) + "Add `X' to the beginning of `XS'." + (vector-concat `[,x] xs)) + +(defun vector-append (x xs) + "Add `X' to the end of `XS'." + (vector-concat xs `[,x])) + +(defun vector-get (i xs) + "Return the value in `XS' at index, `I'." + (aref xs i)) + +(defun vector-set (i v xs) + "Set index `I' to value `V' in `XS'. +Returns a copy of `XS' with the updates." + (let ((copy (vconcat [] xs))) + (aset copy i v) + copy)) + +(defun vector-set! (i v xs) + "Set index `I' to value `V' in `XS'. +This function mutates XS." + (aset xs i v)) + +(when vector-enable-tests? + (let ((xs [1 2 3]) + (ys [1 2 3])) + (prelude-assert (= 1 (vector-get 0 ys))) + (vector-set 0 4 ys) + (prelude-assert (= 1 (vector-get 0 ys))) + (prelude-assert (= 1 (vector-get 0 xs))) + (vector-set! 0 4 xs) + (prelude-assert (= 4 (vector-get 0 xs))))) + +;; TODO: Decide between "remove" and "delete" as the appropriate verbs. +;; TODO: Implement this. +;; (defun vector/delete (i xs) +;; "Remove the element at `I' in `XS'.") + +(provide 'vector) +;;; vector.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/vterm-mgt.el b/users/wpcarro/emacs/.emacs.d/wpc/vterm-mgt.el new file mode 100644 index 000000000..ce60bab14 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/vterm-mgt.el @@ -0,0 +1,129 @@ +;;; vterm-mgt.el --- Help me manage my vterm instances -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Supporting functions to instantiate vterm buffers, kill existing vterm +;; buffers, rename vterm buffers, cycle forwards and backwards through vterm +;; buffers. +;; +;; Many of the functions defined herein are intended to be bound to +;; `vterm-mode-map'. Some assertions are made to guard against calling +;; functions that are intended to be called from outside of a vterm buffer. +;; These assertions shouldn't error when the functions are bound to +;; `vterm-mode-map'. If for some reason, you'd like to bind these functions to +;; a separate keymap, caveat emptor. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'cycle) +(require 'vterm) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup vterm-mgt nil + "Customization options for `vterm-mgt'.") + +(defcustom vterm-mgt-scroll-on-focus nil + "When t, call `end-of-buffer' after focusing a vterm instance." + :type '(boolean) + :group 'vterm-mgt) + +(defconst vterm-mgt--instances (cycle-new) + "A cycle tracking all of my vterm instances.") + +(defun vterm-mgt--instance? (b) + "Return t if the buffer B is a vterm instance." + (equal 'vterm-mode (buffer-local-value 'major-mode b))) + +(defmacro vterm-mgt--assert-vterm-buffer () + "Error when the `current-buffer' is not a vterm buffer." + '(prelude-assert (vterm-mgt--instance? (current-buffer)))) + +(defun vterm-mgt-next () + "Replace the current buffer with the next item in `vterm-mgt--instances'. +This function should be called from a buffer running vterm." + (interactive) + (vterm-mgt--assert-vterm-buffer) + (cycle-focus-item (current-buffer) vterm-mgt--instances) + (switch-to-buffer (cycle-next vterm-mgt--instances)) + (when vterm-mgt-scroll-on-focus (end-of-buffer))) + +(defun vterm-mgt-prev () + "Replace the current buffer with the previous item in `vterm-mgt--instances'. +This function should be called from a buffer running vterm." + (interactive) + (vterm-mgt--assert-vterm-buffer) + (cycle-focus-item (current-buffer) vterm-mgt--instances) + (switch-to-buffer (cycle-prev vterm-mgt--instances)) + (when vterm-mgt-scroll-on-focus (end-of-buffer))) + +(defun vterm-mgt-instantiate () + "Create a new vterm instance. + +Prefer calling this function instead of `vterm'. This function ensures that the + newly created instance is added to `vterm-mgt--instances'. + +If however you must call `vterm', if you'd like to cycle through vterm + instances, make sure you call `vterm-mgt-populate-cycle' to allow vterm-mgt to + collect any untracked vterm instances." + (interactive) + (let ((buffer (vterm))) + (cycle-append buffer vterm-mgt--instances) + (cycle-focus-item buffer vterm-mgt--instances))) + +(defun vterm-mgt-kill () + "Kill the current buffer and remove it from `vterm-mgt--instances'. +This function should be called from a buffer running vterm." + (interactive) + (vterm-mgt--assert-vterm-buffer) + (let ((buffer (current-buffer))) + (cycle-remove buffer vterm-mgt--instances) + (kill-buffer buffer))) + +(defun vterm-mgt-find-or-create () + "Call `switch-to-buffer' on a focused vterm instance if there is one. + +When `cycle-focused?' returns nil, focus the first item in the cycle. When +there are no items in the cycle, call `vterm-mgt-instantiate' to create a vterm +instance." + (interactive) + (if (cycle-empty? vterm-mgt--instances) + (vterm-mgt-instantiate) + (if (cycle-focused? vterm-mgt--instances) + (switch-to-buffer (cycle-current vterm-mgt--instances)) + (progn + (cycle-jump 0 vterm-mgt--instances) + (switch-to-buffer (cycle-current vterm-mgt--instances)))))) + +(defun vterm-mgt-rename-buffer (name) + "Rename the current buffer ensuring that its NAME is wrapped in *vterm*<...>. +This function should be called from a buffer running vterm." + (interactive "SRename vterm buffer: ") + (vterm-mgt--assert-vterm-buffer) + (rename-buffer (format "vterm<%s>" name))) + +(defun vterm-mgt-repopulate-cycle () + "Fill `vterm-mgt--instances' with the existing vterm buffers. + +If for whatever reason, the state of `vterm-mgt--instances' is corrupted and + misaligns with the state of vterm buffers in Emacs, use this function to + restore the state." + (interactive) + (setq vterm-mgt--instances + (->> (buffer-list) + (-filter #'vterm-mgt--instance?) + cycle-from-list))) + +(provide 'vterm-mgt) +;;; vterm-mgt.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/window-manager.el b/users/wpcarro/emacs/.emacs.d/wpc/window-manager.el new file mode 100644 index 000000000..6030461da --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/window-manager.el @@ -0,0 +1,354 @@ +;;; window-manager.el --- Functions augmenting my usage of EXWM -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; I switched to EXWM from i3, and I haven't looked back. One day I may write a +;; poem declaring my love for Emacs and EXWM. For now, I haven't the time. + +;; Wish list: +;; - TODO: Support different startup commands and layouts depending on laptop or +;; desktop. +;; - TODO: Support a Music named-workspace. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'alert) +(require 'al) +(require 'prelude) +(require 'string) +(require 'cycle) +(require 'set) +(require 'kbd) +(require 'ivy-helpers) +(require 'display) +(require 'vterm-mgt) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Associate `window-purpose' window-layouts with each of these named +;; workspaces. + +;; TODO: Associate KBDs for each of these named-layouts. + +;; TODO: Decide between window-manager, exwm, or some other namespace. + +;; TODO: Support (cycle-from-list '(current previous)) to toggle back and forth +;; between most recent workspace. + +;; TODO: Support ad hoc cycle for loading a few workspaces that can be cycled +;; between. (cycle-from-list '("Project" "Workspace")) + +;; TODO: Consider supporting a workspace for Racket, Clojure, Common Lisp, +;; Haskell, Elixir, and a few other languages. These could behave very similarly +;; to repl.it, which I've wanted to have locally for awhile now. + +;; TODO: Support MRU cache of workspaces for easily switching back-and-forth +;; between workspaces. + +(cl-defstruct window-manager--named-workspace label kbd display) + +(defconst window-manager--install-kbds? t + "When t, install the keybindings to switch between named-workspaces.") + +;; TODO: Consume `cache/touch' after changing workspaces. Use this to enable +;; cycling through workspaces. + +(defconst window-manager--named-workspaces + (list (make-window-manager--named-workspace + :label "Web Browsing" + :kbd "c" + :display display-4k-horizontal) + (make-window-manager--named-workspace + :label "Coding" + :kbd "d" + :display display-4k-horizontal) + (make-window-manager--named-workspace + :label "Vertical" + :kbd "h" + :display display-4k-vertical) + (make-window-manager--named-workspace + :label "Laptop" + :kbd "p" + :display display-laptop)) + "List of `window-manager--named-workspace' structs.") + +;; Assert that no two workspaces share KBDs. +(prelude-assert (= (list-length window-manager--named-workspaces) + (->> window-manager--named-workspaces + (list-map #'window-manager--named-workspace-kbd) + set-from-list + set-count))) + +(defun window-manager--alert (x) + "Message X with a structured format." + (alert (string-concat "[exwm] " x))) + +;; Use Emacs as my primary window manager. +(use-package exwm + :config + (require 'exwm-config) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Multiple Displays + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (require 'exwm-randr) + (exwm-randr-enable) + (setq exwm-randr-workspace-monitor-plist + (->> window-manager--named-workspaces + (-map-indexed (lambda (i x) + (list i (window-manager--named-workspace-display x)))) + -flatten)) + (setq exwm-workspace-number (list-length window-manager--named-workspaces)) + (setq exwm-input-simulation-keys + ;; TODO: Consider supporting M-d and other readline style KBDs. + '(([?\C-b] . [left]) + ([?\M-b] . [C-left]) + ([?\C-f] . [right]) + ([?\M-f] . [C-right]) + ([?\C-p] . [up]) + ([?\C-n] . [down]) + ([?\C-a] . [home]) + ([?\C-e] . [end]) + ([?\C-d] . [delete]) + ;; TODO: Assess whether or not this is a good idea. + ;; TODO: Ensure C-c copies. + ([?\C-c] . [C-c]))) + (exwm-enable)) + +;; Here is the code required to allow EXWM to cycle workspaces. +(defconst window-manager--workspaces + (->> window-manager--named-workspaces + cycle-from-list) + "Cycle of the my EXWM workspaces.") + +(prelude-assert + (= exwm-workspace-number + (list-length window-manager--named-workspaces))) + +(defun window-manager-next-workspace () + "Cycle forwards to the next workspace." + (interactive) + (window-manager--change-workspace (cycle-next window-manager--workspaces))) + +(defun window-manager-prev-workspace () + "Cycle backwards to the previous workspace." + (interactive) + (window-manager--change-workspace (cycle-prev window-manager--workspaces))) + +;; TODO: Create friendlier API for working with EXWM. + +;; Here is the code required to toggle EXWM's modes. +(defun window-manager--line-mode () + "Switch exwm to line-mode." + (call-interactively #'exwm-input-grab-keyboard) + (window-manager--alert "Switched to line-mode")) + +(defun window-manager--char-mode () + "Switch exwm to char-mode." + (call-interactively #'exwm-input-release-keyboard) + (window-manager--alert "Switched to char-mode")) + +(defconst window-manager--modes + (cycle-from-list (list #'window-manager--char-mode + #'window-manager--line-mode)) + "Functions to switch exwm modes.") + +(defun window-manager-toggle-mode () + "Switch between line- and char- mode." + (interactive) + (with-current-buffer (window-buffer) + (when (eq major-mode 'exwm-mode) + (funcall (cycle-next window-manager--modes))))) + +;; Ensure exwm apps open in char-mode. +(add-hook 'exwm-manage-finish-hook #'window-manager--char-mode) + +;; Interface to the Linux password manager +;; TODO: Consider writing a better client for this. +(use-package ivy-pass) + +;; TODO: How do I handle this dependency? +(defconst window-manager--preferred-browser "google-chrome" + "My preferred web browser.") + +;; TODO: Consider replacing the `ivy-read' call with something like `hydra' that +;; can provide a small mode for accepting user-input. +;; TODO: Put this somewhere more diliberate. + +;; TODO: Configure the environment variables for xsecurelock so that the font is +;; smaller, different, and the glinux wallpaper doesn't show. +;; - XSECURELOCK_FONT="InputMono-Black 10" +;; - XSECURE_SAVER="" +;; - XSECURE_LOGO_IMAGE="" +;; Maybe just create a ~/.xsecurelockrc +;; TODO: Is there a shell-command API that accepts an alist and serializes it +;; into variables to pass to the shell command? +(defconst window-manager--xsecurelock + "/usr/share/goobuntu-desktop-files/xsecurelock.sh" + "Path to the proper xsecurelock executable. +The other path to xsecurelock is /usr/bin/xsecurelock, which works fine, but it +is not optimized for Goobuntu devices. Goobuntu attempts to check a user's +password using the network. When there is no network connection available, the +login attempts fail with an \"unknown error\", which isn't very helpful. To +avoid this, prefer the goobuntu wrapper around xsecurelock when on a goobuntu +device. This all relates to PAM (i.e. pluggable authentication modules).") + +(defun window-manager-logout () + "Prompt the user for options for logging out, shutting down, etc. + +The following options are supported: +- Lock +- Logout +- Suspend +- Hibernate +- Reboot +- Shutdown + +Ivy is used to capture the user's input." + (interactive) + (let* ((name->cmd `(("Lock" . + (lambda () + (shell-command window-manager--xsecurelock))) + ("Logout" . + (lambda () + (let ((default-directory "/sudo::")) + (shell-command "systemctl stop lightdm")))) + ("Suspend" . + (lambda () + (shell-command "systemctl suspend"))) + ("Hibernate" . + (lambda () + (shell-command "systemctl hibernate"))) + ("Reboot" . + (lambda () + (let ((default-directory "/sudo::")) + (shell-command "reboot")))) + ("Shutdown" . + (lambda () + (let ((default-directory "/sudo::")) + (shell-command "shutdown now"))))))) + (funcall + (lambda () + (funcall (al-get (ivy-read "System: " (al-keys name->cmd)) + name->cmd)))))) + +(defun window-manager--label->index (label workspaces) + "Return the index of the workspace in WORKSPACES named LABEL." + (let ((index (-elem-index label (-map #'window-manager--named-workspace-label + workspaces)))) + (if index index (error (format "No workspace found for label: %s" label))))) + +(defun window-manager--register-kbd (workspace) + "Registers a keybinding for WORKSPACE struct. +Currently using super- as the prefix for switching workspaces." + (let ((handler (lambda () + (interactive) + (window-manager--switch + (window-manager--named-workspace-label workspace)))) + (key (window-manager--named-workspace-kbd workspace))) + (exwm-input-set-key + (kbd-for 'workspace key) + handler))) + +(defun window-manager--change-workspace (workspace) + "Switch EXWM workspaces to the WORKSPACE struct." + (exwm-workspace-switch + (window-manager--label->index + (window-manager--named-workspace-label workspace) + window-manager--named-workspaces)) + (window-manager--alert + (string-format "Switched to: %s" + (window-manager--named-workspace-label workspace)))) + +(defun window-manager--switch (label) + "Switch to a named workspaces using LABEL." + (cycle-focus (lambda (x) + (equal label + (window-manager--named-workspace-label x))) + window-manager--workspaces) + (window-manager--change-workspace (cycle-current window-manager--workspaces))) + +(exwm-input-set-key (kbd "C-S-f") #'window-manager-toggle-previous) + +(defun window-manager-toggle-previous () + "Focus the previously active EXWM workspace." + (interactive) + (window-manager--change-workspace + (cycle-focus-previous! window-manager--workspaces))) + +(defun window-manager--exwm-buffer? (x) + "Return t if buffer X is an EXWM buffer." + (equal 'exwm-mode (buffer-local-value 'major-mode x))) + +(defun window-manager--application-name (buffer) + "Return the name of the application running in the EXWM BUFFER. +This function asssumes that BUFFER passes the `window-manager--exwm-buffer?' +predicate." + (with-current-buffer buffer exwm-class-name)) + +;; TODO: Support disambiguating between two or more instances of the same +;; application. For instance if two `exwm-class-name' values are +;; "Google-chrome", find a encode this information in the `buffer-alist'. +(defun window-manager-switch-to-exwm-buffer () + "Use `completing-read' to focus an EXWM buffer." + (interactive) + (let* ((buffer-alist (->> (buffer-list) + (-filter #'window-manager--exwm-buffer?) + (-map + (lambda (buffer) + (cons (window-manager--application-name buffer) + buffer))))) + (label (completing-read "Switch to EXWM buffer: " buffer-alist))) + (exwm-workspace-switch-to-buffer + (al-get label buffer-alist)))) + +(when window-manager--install-kbds? + (progn + (->> window-manager--named-workspaces + (list-map #'window-manager--register-kbd)) + (window-manager--alert "Registered workspace KBDs!"))) + +(defun window-manager-current-workspace () + "Output the label of the currently active workspace." + (->> window-manager--workspaces + cycle-current + window-manager--named-workspace-label)) + +(defun window-manager-swap-workspaces () + "Prompt the user to switch the current workspace with another." + (interactive) + (let* ((selection (->> window-manager--named-workspaces + (-map #'window-manager--named-workspace-label) + (-reject + (lambda (x) + (s-equals? x (window-manager-current-workspace)))) + (completing-read + (format "Swap current workspace (i.e. \"%s\") with: " + (window-manager-current-workspace))))) + (i (-find-index (lambda (x) + (s-equals? selection (window-manager--named-workspace-label x))) + window-manager--named-workspaces))) + (exwm-workspace-swap exwm-workspace--current (elt exwm-workspace--list i)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Startup Applications in `window-manager--named-workspaces' +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(add-hook 'exwm-init-hook + (lambda () + (display-arrange-primary) + (window-manager--switch "Coding"))) + +(provide 'window-manager) +;;; window-manager.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/window.el b/users/wpcarro/emacs/.emacs.d/wpc/window.el new file mode 100644 index 000000000..ee3c109b7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/window.el @@ -0,0 +1,41 @@ +;;; window.el --- Working with windows -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Utilities to make CRUDing windows in Emacs easier. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'macros) +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun window-find (name) + "Find a window by the NAME of the buffer it's hosting." + (let ((buffer (get-buffer name))) + (if (maybe-some? buffer) + (get-buffer-window buffer) + nil))) + +;; TODO: Find a way to incorporate these into function documentation. +(macros-comment + (window-find "*scratch*")) + +(defun window-delete (window) + "Delete the WINDOW reference." + (delete-window window)) + +(provide 'window) +;;; window.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-clojure.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-clojure.el new file mode 100644 index 000000000..025ef9aab --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-clojure.el @@ -0,0 +1,72 @@ +;;; wpc-clojure.el --- My Clojure preferences -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Hosting my Clojure tooling preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package clojure-mode + :config + ;; from Ryan Schmukler: + (setq cljr-magic-require-namespaces + '(("io" . "clojure.java.io") + ("sh" . "clojure.java.shell") + ("jdbc" . "clojure.java.jdbc") + ("set" . "clojure.set") + ("time" . "java-time") + ("str" . "cuerdas.core") + ("path" . "pathetic.core") + ("walk" . "clojure.walk") + ("zip" . "clojure.zip") + ("async" . "clojure.core.async") + ("component" . "com.stuartsierra.component") + ("http" . "clj-http.client") + ("url" . "cemerick.url") + ("sql" . "honeysql.core") + ("csv" . "clojure.data.csv") + ("json" . "cheshire.core") + ("s" . "clojure.spec.alpha") + ("fs" . "me.raynes.fs") + ("ig" . "integrant.core") + ("cp" . "com.climate.claypoole") + ("re-frame" . "re-frame.core") + ("rf" . "re-frame.core") + ("re" . "reagent.core") + ("reagent" . "reagent.core") + ("u.core" . "utopia.core") + ("gen" . "clojure.spec.gen.alpha")))) + +(use-package cider + :config + (general-define-key + :keymaps 'cider-repl-mode-map + "C-l" #'cider-repl-clear-buffer + "C-u" #'kill-whole-line + "" #'cider-repl-previous-input + "" #'cider-repl-next-input) + (general-define-key + :keymaps 'clojure-mode-map + :states '(normal) + :prefix "" + "x" #'cider-eval-defun-at-point + "X" #'cider-eval-buffer + "d" #'cider-symbol-at-point) + (setq cider-prompt-for-symbol nil)) + +(provide 'wpc-clojure) +;;; wpc-clojure.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-company.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-company.el new file mode 100644 index 000000000..03535621c --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-company.el @@ -0,0 +1,42 @@ +;;; wpc-company.el --- Autocompletion package, company, preferences -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Hosts my company mode preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; autocompletion client +(use-package company + :config + (general-define-key + :keymaps 'company-active-map + "C-j" #'company-select-next + "C-n" #'company-select-next + "C-k" #'company-select-previous + "C-p" #'company-select-previous + "C-d" #'company-show-doc-buffer) + (setq company-tooltip-align-annotations t) + (setq company-idle-delay 0) + (setq company-show-numbers t) + (setq company-minimum-prefix-length 2) + (setq company-dabbrev-downcase nil + company-dabbrev-ignore-case t) + (global-company-mode)) + +(provide 'wpc-company) +;;; wpc-company.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-dired.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-dired.el new file mode 100644 index 000000000..bd2805c73 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-dired.el @@ -0,0 +1,53 @@ +;;; wpc-dired.el --- My dired preferences -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; File management in Emacs, if learned and configured properly, should be +;; capable to reduce my dependency on the terminal. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (require 'dired) + (setq dired-recursive-copies 'always + dired-recursive-deletes 'top + dired-dwim-target t) + (setq dired-listing-switches "-la --group-directories-first") + (general-define-key + :keymaps 'dired-mode-map + :states '(normal) + ;; Overriding some KBDs defined in the evil-collection module. + "o" #'dired-find-file-other-window + "" nil ;; This unblocks some of my leader-prefixed KBDs. + "s" nil ;; This unblocks my window-splitting KBDs. + "c" #'find-file + "f" #'project-find-file + "-" (lambda () (interactive) (find-alternate-file ".."))) + (general-add-hook 'dired-mode-hook + (list (macros-enable dired-hide-details-mode) + #'auto-revert-mode))) + +(progn + (require 'locate) + (general-define-key + :keymaps 'locate-mode-map + :states 'normal + "o" #'dired-find-file-other-window)) + +(provide 'wpc-dired) +;;; wpc-dired.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-elixir.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-elixir.el new file mode 100644 index 000000000..0b5e39171 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-elixir.el @@ -0,0 +1,28 @@ +;;; wpc-elixir.el --- Elixir / Erland configuration -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; My preferences for working with Elixir / Erlang projects + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package elixir-mode + :config + (macros-add-hook-before-save 'elixir-mode-hook #'elixir-format)) + +(provide 'wpc-elixir) +;;; wpc-elixir.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-flycheck.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-flycheck.el new file mode 100644 index 000000000..1f32ce513 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-flycheck.el @@ -0,0 +1,18 @@ +;;; wpc-flycheck.el --- My flycheck configuration -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Hosts my Flycheck preferences + +;;; Code: + +(use-package flycheck + :config + (global-flycheck-mode)) + +(provide 'wpc-flycheck) +;;; wpc-flycheck.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-golang.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-golang.el new file mode 100644 index 000000000..d73249334 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-golang.el @@ -0,0 +1,48 @@ +;;; wpc-golang.el --- Tooling preferences for Go -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Tooling support for golang development. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'macros) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support jumping to go source code for fmt.Println, etc. + +;; I'm unsure if this belongs in wpc-golang.el because it's a generic setting, +;; but because go is the first languages I've encountered that enforces tab +;; usage (I think) I'm configuring it. +(setq-default tab-width 4) + +(use-package go-mode + :config + (setq gofmt-command "goimports") + ;; TODO: Consider configuring `xref-find-definitions' to use `godef-jump' + ;; instead of shadowing the KBD here. + (general-define-key + :states '(normal) + :keymaps '(go-mode-map) + "M-." #'godef-jump) + ;; Support calling M-x `compile'. + (add-hook 'go-mode-hook (lambda () + (set (make-local-variable 'compile-command) + "go build -v"))) + (macros-add-hook-before-save 'go-mode-hook #'gofmt-before-save)) + +(provide 'wpc-golang) +;;; wpc-golang.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-haskell.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-haskell.el new file mode 100644 index 000000000..f9ed8552e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-haskell.el @@ -0,0 +1,62 @@ +;;; wpc-haskell.el --- My Haskell preferences -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Hosts my Haskell development preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; font-locking, glyph support, etc +(use-package haskell-mode + :config + (macros-add-hook-before-save 'haskell-mode #'haskell-align-imports)) + +;; LSP support +(use-package lsp-haskell + :after (haskell-mode) + :config + (setq lsp-haskell-process-path-hie "hie-wrapper") + (add-hook 'haskell-mode-hook #'lsp-haskell-enable) + (add-hook 'haskell-mode-hook #'flycheck-mode)) + +;; Test toggling +(defun wpc-haskell-module->test () + "Jump from a module to a test." + (let ((filename (->> buffer-file-name + (s-replace "/src/" "/test/") + (s-replace ".hs" "Test.hs") + find-file))) + (make-directory (f-dirname filename) t) + (find-file filename))) + +(defun wpc-haskell-test->module () + "Jump from a test to a module." + (let ((filename (->> buffer-file-name + (s-replace "/test/" "/src/") + (s-replace "Test.hs" ".hs")))) + (make-directory (f-dirname filename) t) + (find-file filename))) + +(defun wpc-haskell-test<->module () + "Toggle between test and module in Haskell." + (interactive) + (if (s-contains? "/src/" buffer-file-name) + (wpc-haskell-module->test) + (wpc-haskell-test->module))) + +(provide 'wpc-haskell) +;;; wpc-haskell.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-javascript.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-javascript.el new file mode 100644 index 000000000..da84df66d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-javascript.el @@ -0,0 +1,99 @@ +;;; wpc-javascript.el --- My Javascript preferences -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This module hosts my Javascript tooling preferences. This also includes +;; tooling for TypeScript and other frontend tooling. Perhaps this module will +;; change names to more accurately reflect that. +;; +;; Depends +;; - yarn global add prettier + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Constants +(defconst wpc-javascript--js-hooks + '(js-mode-hook + web-mode-hook + typescript-mode-hook + js2-mode-hook + rjsx-mode-hook) + "All of the commonly used hooks for Javascript buffers.") + +(defconst wpc-javascript--frontend-hooks + (-insert-at 0 'css-mode-hook wpc-javascript--js-hooks) + "All of the commonly user hooks for frontend development.") + +;; frontend indentation settings +(setq typescript-indent-level 2 + js-indent-level 2 + css-indent-offset 2) + +;; Flow for Javascript +(use-package add-node-modules-path + :config + (general-add-hook wpc-javascript--js-hooks #'add-node-modules-path)) + +(use-package web-mode + :mode "\\.html\\'" + :config + (setq web-mode-css-indent-offset 2) + (setq web-mode-code-indent-offset 2) + (setq web-mode-markup-indent-offset 2)) + +;; JSX highlighting +(use-package rjsx-mode + :config + (general-unbind rjsx-mode-map "<" ">" "C-d") + (general-nmap + :keymaps 'rjsx-mode-map + "K" #'flow-minor-type-at-pos) + (setq js2-mode-show-parse-errors nil + js2-mode-show-strict-warnings nil)) + +(progn + (defun wpc-javascript-tide-setup () + (interactive) + (tide-setup) + (flycheck-mode 1) + (setq flycheck-check-syntax-automatically '(save mode-enabled)) + (eldoc-mode 1) + (tide-hl-identifier-mode 1) + (company-mode 1)) + (use-package tide + :config + (add-hook 'typescript-mode-hook #'wpc-javascript-tide-setup)) + (require 'web-mode) + (add-to-list 'auto-mode-alist '("\\.tsx\\'" . web-mode)) + (add-hook 'web-mode-hook + (lambda () + (when (string-equal "tsx" (f-ext buffer-file-name)) + (wpc-javascript-tide-setup)))) + (flycheck-add-mode 'typescript-tslint 'web-mode)) + +;; JS autoformatting +(use-package prettier-js + :config + (general-add-hook wpc-javascript--frontend-hooks #'prettier-js-mode)) + +;; Support Elm +(use-package elm-mode + :config + (add-hook 'elm-mode-hook #'elm-format-on-save-mode)) + +(provide 'wpc-javascript) +;;; wpc-javascript.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-lisp.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-lisp.el new file mode 100644 index 000000000..f4f8c6931 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-lisp.el @@ -0,0 +1,116 @@ +;;; wpc-lisp.el --- Generic LISP preferences -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; parent (up) +;; child (down) +;; prev-sibling (left) +;; next-sibling (right) + +;;; Code: + +;; TODO: Consider having a separate module for each LISP dialect. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst wpc-lisp--hooks + '(lisp-mode-hook + emacs-lisp-mode-hook + clojure-mode-hook + clojurescript-mode-hook + racket-mode-hook) + "List of LISP modes.") + +(use-package sly + :config + (setq inferior-lisp-program "sbcl") + (general-define-key + :keymaps 'sly-mode-map + :states '(normal) + :prefix "" + "x" #'sly-eval-defun + "X" #'sly-eval-buffer + "d" #'sly-describe-symbol)) + +(use-package rainbow-delimiters + :config + (general-add-hook wpc-lisp--hooks #'rainbow-delimiters-mode)) + +(use-package racket-mode + :config + (general-define-key + :keymaps 'racket-mode-map + :states 'normal + :prefix "" + "x" #'racket-send-definition + "X" #'racket-run + "d" #'racket-describe) + (setq racket-program "~/.nix-profile/bin/racket")) + +(use-package lispyville + :init + (defconst wpc-lisp--lispyville-key-themes + '(c-w + operators + text-objects + prettify + commentary + slurp/barf-cp + wrap + additional + additional-insert + additional-wrap + escape) + "All available key-themes in Lispyville.") + :config + (general-add-hook wpc-lisp--hooks #'lispyville-mode) + (lispyville-set-key-theme wpc-lisp--lispyville-key-themes) + (progn + (general-define-key + :keymaps 'lispyville-mode-map + :states 'motion + ;; first unbind + "M-h" nil + "M-l" nil) + (general-define-key + :keymaps 'lispyville-mode-map + :states 'normal + ;; first unbind + "M-j" nil + "M-k" nil + ;; second rebind + "C-s-h" #'lispyville-drag-backward + "C-s-l" #'lispyville-drag-forward + "C-s-e" #'lispyville-end-of-defun + "C-s-a" #'lispyville-beginning-of-defun))) + +;; Elisp +(use-package elisp-slime-nav + :config + (general-add-hook 'emacs-lisp-mode #'ielm-mode)) + +(general-define-key + :keymaps 'emacs-lisp-mode-map + :prefix "" + :states 'normal + "x" #'eval-defun + "X" #'eval-buffer + "d" (lambda () + (interactive) + (with-current-buffer (current-buffer) + (helpful-function (symbol-at-point))))) + +(provide 'wpc-lisp) +;;; wpc-lisp.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-misc.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-misc.el new file mode 100644 index 000000000..574162fbf --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-misc.el @@ -0,0 +1,315 @@ +;;; wpc-misc.el --- Hosting miscellaneous configuration -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) +;; URL: https://git.wpcarro.dev/wpcarro/briefcase + +;;; Commentary: +;; This is the home of any configuration that couldn't find a better home. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'project) +(require 'f) +(require 'dash) +(require 'constants) +(require 'region) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq display-time-string-forms + '((format-time-string "%H:%M %a %b %d"))) +(display-time-mode 1) + +;; Remove the boilerplate in the *scratch* buffer +(setq initial-scratch-message "") + +;; disable custom variable entries from being written to ~/.emacs.d/init.el +(setq custom-file (f-join user-emacs-directory "custom.el")) +(load custom-file 'noerror) + +;; integrate Emacs with X11 clipboard +(setq select-enable-primary t) +(setq select-enable-clipboard t) +(general-def 'insert + "s-v" #'clipboard-yank + "C-S-v" #'clipboard-yank) + +;; transparently edit compressed files +(auto-compression-mode t) + +;; autowrap when over the fill-column +(setq-default auto-fill-function #'do-auto-fill) + +;; link to Emacs source code +;; TODO: Update this link. +(setq find-function-C-source-directory + "~/Dropbox/programming/emacs/src") + +;; change emacs prompts from "yes or no" -> "y or n" +(fset 'yes-or-no-p 'y-or-n-p) + +;; open photos in Emacs +(auto-image-file-mode 1) + +;; disable line-wrapping +(setq-default truncate-lines 1) + +;; shell file indentation +(setq sh-basic-offset 2) +(setq sh-indentation 2) + +;; Emacs library that interfaces with my Linux password manager. +(use-package password-store) + +(use-package vterm + :config + (general-define-key + :keymaps '(vterm-mode-map) + :states '(insert) + "C-S-v" #'vterm-yank) + (general-define-key + :keymaps '(vterm-mode-map) + :states '(normal) + "K" #'evil-scroll-line-up + "J" #'evil-scroll-line-down + "C-b" #'evil-scroll-page-up + "C-f" #'evil-scroll-page-down)) + +;; Use en Emacs buffer as a REST client. +;; For more information: http://emacsrocks.com/e15.html +(use-package restclient) + +;; Run `package-lint' before publishing to MELPA. +(use-package package-lint) + +;; Parser combinators in Elisp. +(use-package parsec) + +;; disable company mode when editing markdown +;; TODO: move this out of wpc-misc.el and into a later file to call +;; `(disable company-mode)' +(use-package markdown-mode + :config + ;; TODO: Add assertion that pandoc is installed and it is accessible from + ;; Emacs. + (setq markdown-command "pandoc") + (setq markdown-split-window-direction 'right) + ;; (add-hook 'markdown-mode-hook #'markdown-live-preview-mode) + ) + +(use-package alert) + +(use-package refine) + +;; Required by some google-emacs package commands. +(use-package deferred) + +;; git integration +(use-package magit + :config + (add-hook 'git-commit-setup-hook + (lambda () + (company-mode -1) + (flyspell-mode 1))) + (setq magit-display-buffer-function + #'magit-display-buffer-fullframe-status-v1)) + +(use-package magit-popup) + +;; http +(use-package request) + +;; perl-compatible regular expressions +(use-package pcre2el) + +;; alternative to help +(use-package helpful) + +;; If called from an existing helpful-mode buffer, reuse that buffer; otherwise, +;; call `pop-to-buffer'. +(setq helpful-switch-buffer-function + (lambda (buffer-or-name) + (if (eq major-mode 'helpful-mode) + (switch-to-buffer buffer-or-name) + (pop-to-buffer buffer-or-name)))) + +;; Emacs integration with direnv +(use-package direnv + :config + (direnv-mode)) + +;; Superior Elisp library for working with dates and times. +;; TODO: Put this where my other installations for dash.el, s.el, a.el, and +;; other utility Elisp libraries are located. +(use-package ts) + +;; persist history etc b/w Emacs sessions +(setq desktop-save 'if-exists) +(desktop-save-mode 1) +(setq desktop-globals-to-save + (append '((extended-command-history . 30) + (file-name-history . 100) + (grep-history . 30) + (compile-history . 30) + (minibuffer-history . 50) + (query-replace-history . 60) + (read-expression-history . 60) + (regexp-history . 60) + (regexp-search-ring . 20) + (search-ring . 20) + (shell-command-history . 50) + tags-file-name + register-alist))) + +;; configure ibuffer +(setq ibuffer-default-sorting-mode 'major-mode) + +;; config Emacs to use $PATH values +(use-package exec-path-from-shell + :if (memq window-system '(mac ns)) + :config + (exec-path-from-shell-initialize)) + +;; Emacs autosave, backup, interlocking files +(setq auto-save-default nil + make-backup-files nil + create-lockfiles nil) + +;; ensure code wraps at 80 characters by default +(setq-default fill-column constants-fill-column) + +(put 'narrow-to-region 'disabled nil) + +;; trim whitespace on save +(add-hook 'before-save-hook #'delete-trailing-whitespace) + +;; call `git secret hide` after saving secrets.json +(add-hook 'after-save-hook + (lambda () + (when (f-equal? (buffer-file-name) + (f-join constants-briefcase "secrets.json")) + (shell-command "git secret hide")))) + +;; use tabs instead of spaces +(setq-default indent-tabs-mode nil) + +;; automatically follow symlinks +(setq vc-follow-symlinks t) + +;; fullscreen settings +(setq ns-use-native-fullscreen nil) + +(use-package yasnippet + :config + (setq yas-snippet-dirs (list (f-join user-emacs-directory "snippets"))) + (yas-global-mode 1)) + +(use-package projectile + :config + (projectile-mode t)) + +;; TODO: Consider moving this into a briefcase.el module. +(defun wpc-misc--briefcase-find (dir) + "Find the default.nix nearest to DIR." + ;; I use 'vc only at the root of my monorepo because 'transient doesn't use my + ;; .gitignore, which slows things down. Ideally, I could write a version that + ;; behaves like 'transient but also respects my monorepo's .gitignore and any + ;; ancestor .gitignore files. + (if (f-equal? constants-briefcase dir) + (cons 'vc dir) + (when (f-ancestor-of? constants-briefcase dir) + (if (f-exists? (f-join dir "default.nix")) + (cons 'transient dir) + (wpc-misc--briefcase-find (f-parent dir)))))) + +(add-to-list 'project-find-functions #'wpc-misc--briefcase-find) + +(defun wpc-misc-pkill (name) + "Call the pkill executable using NAME as its argument." + (interactive "sProcess name: ") + (call-process "pkill" nil nil nil name)) + +(use-package deadgrep + :config + (general-define-key + :keymaps 'deadgrep-mode-map + :states 'normal + "o" #'deadgrep-visit-result-other-window) + (setq-default deadgrep--context '(0 . 3)) + (defun wpc-misc-deadgrep-region () + "Run a ripgrep search on the active region." + (interactive) + (deadgrep (region-to-string))) + (defun wpc-misc-deadgrep-dwim () + "If a region is active, use that as the search, otherwise don't." + (interactive) + (with-current-buffer (current-buffer) + (if (region-active-p) + (setq deadgrep--additional-flags '("--multiline")) + (wpc-misc-deadgrep-region) + (call-interactively #'deadgrep)))) + (advice-add + 'deadgrep--format-command + :filter-return + (lambda (cmd) + (replace-regexp-in-string + "^rg " "rg --hidden " cmd)))) + +;; TODO: Do I need this when I have swiper? +(use-package counsel) + +(use-package counsel-projectile) + +;; search Google, Stackoverflow from within Emacs +(use-package engine-mode + :config + (defengine google + "http://www.google.com/search?ie=utf-8&oe=utf-8&q=%s" + :keybinding "g") + (defengine stack-overflow + "https://stackoverflow.com/search?q=%s" + :keybinding "s")) + +;; EGlot (another LSP client) +(use-package eglot) + +;; Microsoft's Debug Adapter Protocol (DAP) +(use-package dap-mode + :after lsp-mode + :config + (dap-mode 1) + (dap-ui-mode 1)) + +;; Microsoft's Language Server Protocol (LSP) +(use-package lsp-ui + :config + (add-hook 'lsp-mode-hook #'lsp-ui-mode)) + +(use-package company-lsp + :config + (push 'company-lsp company-backends)) + +;; Wilfred/suggest.el - Tool for discovering functions basesd on declaring your +;; desired inputs and outputs. +(use-package suggest) + +;; Malabarba/paradox - Enhances the `list-packages' view. +(use-package paradox + :config + (paradox-enable)) + +;; Start the Emacs server +(when (not (server-running-p)) + (server-start)) + +(provide 'wpc-misc) +;;; wpc-misc.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-nix.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-nix.el new file mode 100644 index 000000000..a555e4621 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-nix.el @@ -0,0 +1,72 @@ +;;; wpc-nix.el --- Nix support -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Configuration to support working with Nix. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'device) +(require 'constants) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package nix-mode + :mode "\\.nix\\'") + +;; TODO(wpcarro): Ensure the sub-process can resolve . +(defun wpc-nix-rebuild-emacs () + "Use nix-env to rebuild wpcarros-emacs." + (interactive) + (let* ((pname (format "nix-build ")) + (bname (format "*%s*" pname))) + (start-process pname bname + "nix-env" + "-I" (format "briefcase=%s" constants-briefcase) + "-f" "" "-iA" "emacs.nixos") + (display-buffer bname))) + +(defun wpc-nix-sly-from-briefcase (attr) + "Start a Sly REPL configured using the derivation pointed at by ATTR. + + The derivation invokes nix.buildLisp.sbclWith and is built asynchronously. + The build output is included in the error thrown on build failures." + (interactive "sAttribute: ") + (lexical-let* ((outbuf (get-buffer-create (format "*briefcase-out/%s*" attr))) + (errbuf (get-buffer-create (format "*briefcase-errors/%s*" attr))) + (expression (format "let briefcase = import {}; in briefcase.third_party.depot.nix.buildLisp.sbclWith [ briefcase.%s ]" attr)) + (command (list "nix-build" "-E" expression))) + (message "Acquiring Lisp for .%s" attr) + (make-process :name (format "nix-build/%s" attr) + :buffer outbuf + :stderr errbuf + :command command + :sentinel + (lambda (process event) + (unwind-protect + (pcase event + ("finished\n" + (let* ((outpath (s-trim (with-current-buffer outbuf + (buffer-string)))) + (lisp-path (s-concat outpath "/bin/sbcl"))) + (message "Acquired Lisp for .%s at %s" + attr lisp-path) + (sly lisp-path))) + (_ (with-current-buffer errbuf + (error "Failed to build '%s':\n%s" attr + (buffer-string))))) + (kill-buffer outbuf) + (kill-buffer errbuf)))))) + +(provide 'wpc-nix) +;;; wpc-nix.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-org.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-org.el new file mode 100644 index 000000000..bed4dd067 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-org.el @@ -0,0 +1,40 @@ +;;; wpc-org.el --- My org preferences -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.1")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Hosts my org mode preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'macros) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package org + :config + (evil-set-initial-state 'org-mode 'normal) + (general-add-hook 'org-mode-hook + (list (macros-disable linum-mode) + (macros-disable company-mode))) + (setq org-startup-folded nil) + (setq org-todo-keywords '((sequence "TODO" "BLOCKED" "DONE"))) + (general-unbind 'normal org-mode-map "M-h" "M-j" "M-k" "M-l")) + +(use-package org-bullets + :config + (general-add-hook 'org-mode-hook (macros-enable org-bullets-mode))) + +(provide 'wpc-org) +;;; wpc-org.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-package.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-package.el new file mode 100644 index 000000000..3a363df8e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-package.el @@ -0,0 +1,33 @@ +;;; wpc-package.el --- My package configuration -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.1")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; This module hosts all of the settings required to work with ELPA, +;; MELPA, QUELPA, and co. + +;;; Code: + +(require 'package) + +;; Even though we're packaging our Emacs with Nix, having MELPA registered is +;; helpful to ad-hoc test out packages before declaratively adding them to +;; emacs/default.nix. +(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/")) +(package-initialize) + +(unless (package-installed-p 'use-package) + ;; TODO: Consider removing this to improve initialization speed. + (package-refresh-contents) + (package-install 'use-package)) +(eval-when-compile + (require 'use-package)) +;; TODO: Consider removing this, since I'm requiring general.el in individual +;; modules. +(use-package general) + +(provide 'wpc-package) +;;; wpc-package.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-prolog.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-prolog.el new file mode 100644 index 000000000..b891a7df6 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-prolog.el @@ -0,0 +1,20 @@ +;;; wpc-prolog.el --- For Prologging things -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Code configuring my Prolog work. + +;;; Code: + +(require 'macros) + +;; TODO: Notice that the .pl extension conflicts with Perl files. This may +;; become a problem should I start working with Perl. +(macros-support-file-extension "pl" prolog-mode) + +(provide 'wpc-prolog) +;;; wpc-prolog.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-python.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-python.el new file mode 100644 index 000000000..cd30f3ea3 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-python.el @@ -0,0 +1,25 @@ +;;; wpc-python.el --- Python configuration -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; My Python configuration settings +;; +;; Depends +;; - `apti yapf` + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package py-yapf + :config + (add-hook 'python-mode-hook #'py-yapf-enable-on-save)) + +(provide 'wpc-python) +;;; wpc-python.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-rust.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-rust.el new file mode 100644 index 000000000..396d6349a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-rust.el @@ -0,0 +1,48 @@ +;;; wpc-rust.el --- Support Rust language -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Supports my Rust work. +;; +;; Dependencies: +;; - `rustup` +;; - `rustup component add rust-src` +;; - `rustup toolchain add nightly && cargo +nightly install racer` + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package racer + :config + (setq rust-sysroot (->> "~/.cargo/bin/rustc --print sysroot" + shell-command-to-string + s-trim-right)) + (setq racer-rust-src-path (f-join rust-sysroot "lib/rustlib/src/rust/src")) + (add-hook 'racer-mode-hook #'eldoc-mode)) + +(use-package rust-mode + :config + (add-hook 'rust-mode-hook #'racer-mode) + (macros-add-hook-before-save 'rust-mode-hook #'rust-format-buffer) + (define-key rust-mode-map + (kbd "TAB") + #'company-indent-or-complete-common) + (define-key rust-mode-map + (kbd "M-d") + #'racer-describe)) + +(provide 'wpc-rust) +;;; wpc-rust.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-shell.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-shell.el new file mode 100644 index 000000000..855f234b2 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-shell.el @@ -0,0 +1,32 @@ +;;; wpc-shell.el --- POSIX Shell scripting support -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Helpers for my shell scripting. Includes bash, zsh, etc. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'zle) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Code +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package flymake-shellcheck + :commands flymake-shellcheck-load + :init + (add-hook 'sh-mode-hook #'flymake-shellcheck-load) + (add-hook 'sh-mode-hook #'zle-minor-mode)) + +(use-package fish-mode) + +(provide 'wpc-shell) +;;; wpc-shell.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-ui.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-ui.el new file mode 100644 index 000000000..fd025ddd8 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-ui.el @@ -0,0 +1,181 @@ +;;; wpc-ui.el --- Any related to the UI/UX goes here -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Hosts font settings, scrolling, color schemes. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'constants) +(require 'prelude) +(require 'al) +(require 'fonts) +(require 'colorscheme) +(require 'device) +(require 'laptop-battery) +(require 'modeline) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; line height +(setq-default line-spacing 0) + +(when window-system + (setq frame-title-format '(buffer-file-name "%f" ("%b")))) + +;; Ensure that buffers update when their contents change on disk. +(global-auto-revert-mode t) + +;; smooth scrolling settings +(setq scroll-step 1 + scroll-conservatively 10000) + +;; clean up modeline +(use-package diminish + :config + (diminish 'emacs-lisp-mode "elisp") + (diminish 'evil-commentary-mode) + (diminish 'flycheck-mode) + (diminish 'auto-revert-mode) + (diminish 'which-key-mode) + (diminish 'yas-minor-mode) + (diminish 'lispyville-mode) + (diminish 'undo-tree-mode) + (diminish 'company-mode) + (diminish 'projectile-mode) + (diminish 'eldoc-mode) + ;; This is how to diminish `auto-fill-mode'. + (diminish 'auto-fill-function) + (diminish 'counsel-mode) + (diminish 'ivy-mode)) + +;; TODO: Further customize `mode-line-format' variable. +(delete 'mode-line-modes mode-line-format) +(delete '(vc-mode vc-mode) mode-line-format) + +;; disable startup screen +(setq inhibit-startup-screen t) + +;; disable toolbar +(tool-bar-mode -1) + +;; set default buffer for Emacs +(setq initial-buffer-choice constants-current-project) + +;; premium Emacs themes +(use-package doom-themes + :config + (setq doom-themes-enable-bold t + doom-themes-enable-italic t) + (doom-themes-visual-bell-config) + (doom-themes-org-config)) + +;; kbd discovery +(use-package which-key + :config + (setq which-key-idle-delay 0.25) + (which-key-mode)) + +;; completion framework +(use-package ivy + :config + (counsel-mode t) + (ivy-mode t) + ;; Remove preceding "^" from ivy prompts + (setq ivy-initial-inputs-alist nil) + ;; prefer using `helpful' variants + (progn + (setq counsel-describe-function-function #'helpful-callable) + (setq counsel-describe-variable-function #'helpful-variable)) + (general-define-key + :keymaps '(ivy-minibuffer-map ivy-switch-buffer-map) + ;; prev + "C-k" #'ivy-previous-line + "" #'ivy-previous-line + ;; next + "C-j" #'ivy-next-line + "" #'ivy-next-line)) + +(use-package ivy-prescient + :config + (ivy-prescient-mode 1) + (prescient-persist-mode 1)) + +;; all-the-icons +(use-package all-the-icons + :config + (when (not constants-ci?) + (unless (f-exists? "~/.local/share/fonts/all-the-icons.ttf") + (all-the-icons-install-fonts t)))) + +;; icons for Ivy +(use-package all-the-icons-ivy + :after (ivy all-the-icons) + :config + (all-the-icons-ivy-setup)) + +;; disable menubar +(menu-bar-mode -1) + +;; reduce noisiness of auto-revert-mode +(setq auto-revert-verbose nil) + +;; highlight lines that are over `constants-fill-column' characters long +(use-package whitespace + :config + ;; TODO: This should change depending on the language and project. For + ;; example, Google Java projects prefer 100 character width instead of 80 + ;; character width. + (setq whitespace-line-column constants-fill-column) + (setq whitespace-style '(face lines-tail)) + (add-hook 'prog-mode-hook #'whitespace-mode)) + +;; dirname/filename instead of filename +(setq uniquify-buffer-name-style 'forward) + +;; highlight matching parens, brackets, etc +(show-paren-mode 1) + +;; hide the scroll-bars in the GUI +(scroll-bar-mode -1) + +;; TODO: Learn how to properly integrate this with dunst or another system-level +;; notification program. +;; GUI alerts in emacs +(use-package alert + :commands (alert) + :config + (setq alert-default-style 'notifier)) + +;; TODO: Should `device-work-laptop?' be a function or a constant that gets set +;; during initialization? +(when (device-work-laptop?) + (laptop-battery-display)) + +(if window-system + (progn + (fonts-whitelist-set "JetBrainsMono") + (fonts-enable-ligatures) + (colorscheme-whitelist-set 'doom-solarized-light) + ;; the doom-acario-dark theme uses "Monospace Serif" as the font for + ;; comments, and I'd prefer JetBrainsMono (no italics). + (set-face-attribute font-lock-comment-face nil + :family "JetBrainsMono" + :slant 'normal)) + (load-theme 'wombat)) + +(modeline-setup) + +(provide 'wpc-ui) +;;; wpc-ui.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/zle.el b/users/wpcarro/emacs/.emacs.d/wpc/zle.el new file mode 100644 index 000000000..0e0baad2d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/zle.el @@ -0,0 +1,92 @@ +;;; zle.el --- Functions to mimmick my ZLE KBDs -*- lexical-binding: t -*- + +;; Author: William Carroll +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This is primarily for personal use. The keybindings that I choose are those +;; that feel slightly mnemonic while also not shadowing important bindings. +;; It's quite possible that our tastes will differ here. +;; +;; All of these keybindings are intended to shave off milliseconds off your +;; typing. I don't expect these numbers to sum up to a meaningful amount. The +;; primary reason that I wrote this, is that it introduces a small amount of +;; structural editing to my workflow. I've been using these exact keybindings +;; on the command line, and I find them subtely delightful to use. So much so +;; that I decided to bring them to my Emacs configuration. +;; +;; ZLE is the Z-shell line editor. I have some KBDs and functions that I often +;; want in Emacs. +;; +;; Usage: +;; Consider running `(zle-minor-mode)' to run this globally. Depending on your +;; configuration, it could be non-disruptive, disruptive, or extremely +;; disruptive. + +;;; Code: + +;; subshell (C-j) +(defun zle-subshell () + "Insert the characters necessary to create a subshell." + (interactive) + (insert-char ?$) + (insert-char ?\() + (save-excursion + (insert-char ?\)))) + +;; variable (C-v) +(defun zle-variable () + "Insert the characters to reference a variable." + (interactive) + (insert-char ?$) + (insert-char ?{) + (save-excursion + (insert-char ?}))) + +;; 2x dash (C-M--) +(defun zle-dash-dash () + "Insert the characters for flags with 2x dashes." + (interactive) + (insert-char ? ) + (insert-char ?-) + (insert-char ?-)) + +;; 1x quotes (M-') +(defun zle-single-quote () + "Insert the characters to quickly create single quotes." + (interactive) + (insert-char ? ) + (insert-char ?') + (save-excursion + (insert-char ?'))) + +;; 2x quotes (M-") +(defun zle-double-quote () + "Insert the characters to quickly create double quotes." + (interactive) + (insert-char ? ) + (insert-char ?\") + (save-excursion + (insert-char ?\"))) + +(defvar zle-kbds + (let ((map (make-sparse-keymap))) + (bind-keys :map map + ("C-j" . zle-subshell) + ("C-v" . zle-variable) + ("C-M--" . zle-dash-dash) + ("M-'" . zle-single-quote) + ("M-\"" . zle-double-quote)) + map) + "Keybindings shaving milliseconds off of typing.") + +(define-minor-mode zle-minor-mode + "A minor mode mirroring my ZLE keybindings." + :init-value nil + :lighter " zle" + :keymap zle-kbds) + +(provide 'zle) +;;; zle.el ends here diff --git a/users/wpcarro/emacs/README.md b/users/wpcarro/emacs/README.md new file mode 100644 index 000000000..b4b16b328 --- /dev/null +++ b/users/wpcarro/emacs/README.md @@ -0,0 +1,13 @@ +# Emacs + +Emacs is one of a handful software projects that I highly value. I consider it +as central to my workflow as `git` and `nix`. + +## Installing + +If you already have `briefcase` on your local file system, run the following +from the top-level `briefcase` directory: + +```shell +$ nix-build -f . -iA emacs.nixos +``` diff --git a/users/wpcarro/emacs/default.nix b/users/wpcarro/emacs/default.nix new file mode 100644 index 000000000..4c50d3294 --- /dev/null +++ b/users/wpcarro/emacs/default.nix @@ -0,0 +1,191 @@ +{ pkgs, depot, ... }: + +let + inherit (builtins) path; + inherit (depot.third_party) emacsPackagesGen emacs27; + inherit (pkgs) writeShellScript writeShellScriptBin; + inherit (pkgs.lib.strings) concatStringsSep makeBinPath; + + emacsBinPath = makeBinPath (with pkgs; [ + ripgrep + bat + fd + fzf + pass + tokei + nmap + tldr + diskus + jq + pup + exa + gitAndTools.hub + kubectl + google-cloud-sdk + xsv + scrot + clipmenu + xorg.xset + direnv + nix + ]); + + emacsWithPackages = (emacsPackagesGen emacs27).emacsWithPackages; + + wpcarrosEmacs = emacsWithPackages (epkgs: + (with epkgs.elpaPackages; [ + exwm + ]) ++ + + (with epkgs.melpaPackages; [ + org-bullets + sly + notmuch + elm-mode + ts + vterm + base16-theme + password-store + clipmon # TODO: Prefer an Emacs client for clipmenud. + evil + evil-collection + evil-magit + evil-commentary + evil-surround + key-chord + add-node-modules-path # TODO: Assess whether or not I need this with Nix. + web-mode + rjsx-mode + tide + prettier-js + flycheck + diminish + doom-themes + telephone-line + which-key + all-the-icons + all-the-icons-ivy + ivy + ivy-pass + ivy-prescient + restclient + package-lint + parsec + magit-popup + direnv + alert + nix-mode + racer + rust-mode + rainbow-delimiters + racket-mode + lispyville + elisp-slime-nav + py-yapf + reason-mode + elixir-mode + go-mode + company + markdown-mode + refine + deferred + magit + request + pcre2el + helpful + exec-path-from-shell # TODO: Determine if Nix solves this problem. + yasnippet + projectile + deadgrep + counsel + counsel-projectile + engine-mode # TODO: Learn what this is. + eglot + dap-mode + lsp-ui + company-lsp + suggest + paradox + flymake-shellcheck + fish-mode + tuareg + haskell-mode + lsp-haskell + use-package + general + clojure-mode + cider + f + dash + company + counsel + flycheck + ])); + + vendorDir = path { + path = ./.emacs.d/vendor; + name = "emacs-vendor"; + }; + + # TODO: byte-compile these by packaging each as an Elisp library. + wpcDir = path { + path = ./.emacs.d/wpc; + name = "emacs-libs"; + }; + + wpcPackageEl = path { + path = ./.emacs.d/wpc/wpc-package.el; + name = "wpc-package.el"; + }; + + initEl = path { + path = ./.emacs.d/init.el; + name = "init.el"; + }; + + loadPath = concatStringsSep ":" [ + wpcDir + vendorDir + # TODO: Explain why the trailing ":" is needed. + "${wpcarrosEmacs.deps}/share/emacs/site-lisp:" + ]; + + withEmacsPath = { emacsBin, briefcasePath ? "$HOME/briefcase" }: + writeShellScriptBin "wpcarros-emacs" '' + export XMODIFIERS=emacs + export BRIEFCASE=${briefcasePath} + export GOOGLE_BRIEFCASE="$HOME/google-briefcase" + export PATH="${emacsBinPath}:$PATH" + export EMACSLOADPATH="${loadPath}" + exec ${emacsBin} \ + --debug-init \ + --no-init-file \ + --no-site-file \ + --no-site-lisp \ + --load ${initEl} \ + "$@" + ''; +in { + inherit initEl withEmacsPath; + + # I need to start my Emacs from CI without the call to `--load ${initEl}`. + runScript = { script, briefcasePath }: + writeShellScript "run-emacs-script" '' + export BRIEFCASE=${briefcasePath} + export PATH="${emacsBinPath}:$PATH" + export EMACSLOADPATH="${wpcDir}:${vendorDir}:${wpcarrosEmacs.deps}/share/emacs/site-lisp" + exec ${wpcarrosEmacs}/bin/emacs \ + --no-site-file \ + --no-site-lisp \ + --no-init-file \ + --script ${script} \ + "$@" + ''; + + # Use `nix-env -f '' emacs.nixos` to install `wpcarros-emacs` on + # NixOS machines. + nixos = { briefcasePath ? "$HOME/briefcase" }: withEmacsPath { + inherit briefcasePath; + emacsBin = "${wpcarrosEmacs}/bin/emacs"; + }; +} diff --git a/users/wpcarro/emacs/elisp-conventions.md b/users/wpcarro/emacs/elisp-conventions.md new file mode 100644 index 000000000..0e39c3069 --- /dev/null +++ b/users/wpcarro/emacs/elisp-conventions.md @@ -0,0 +1,20 @@ +# Elisp Conventions + +Some of this aligns with existing style guides. Some of it does not. + +In general, prefer functions with fixed arities instead of variadic +alternatives. + +- Namespace functions with `namespace/function-name` +- Use `ensure`, `assert`, `refute` whenever possible. +- When talking about encoding and decoding, let's use the words "encoding" and + "decoding" rather than the myriad of other variants that appear like: + - `marshalling` and `unmarshalling` + - `parse` and `deparse`, `serialize`, `stringify` + - `unpickle` and `pickle` (Python) + - `from-string` and `to-string` + - TODO: Add more examples of these; there should be close to a dozen. +- Annotate assertions with `!` endings. +- Prefer the Scheme style of `predicate?` +- Variadic functions *should* encode this by appending * onto their + name. E.g. `maybe/nil?*` diff --git a/users/wpcarro/emacs/keybindings.md b/users/wpcarro/emacs/keybindings.md new file mode 100644 index 000000000..96ba7c964 --- /dev/null +++ b/users/wpcarro/emacs/keybindings.md @@ -0,0 +1,47 @@ +# Keybindings + +Since I'm using Emacs to manage most of my workflow, all of the keybindings +should be defined herein and -- in order to scale -- order must be imposed. This +can help avoid KBD collisions and improve my ability to remember each KBD. + +See `kbd.el` for the programmatic encoding of these principles. + +## Troubleshooting + +When in doubt, use Emacs's `read-key` and `read-event` to learn what signal +you're sending Emacs. + +### Super- + +- EXWM X11 windows are not processing `s-`. +- EXWM X11 windows are not processing ``. + +### Super-Ctrl- + +I'm reserving `C-s-` for opening X11 applications. + +- `terminator`: `t` +- `google-chrome`: `c` + +## Emacs nouns + +Most of my keybindings should be organized according to their function, which in +turn should be related to the following Emacs nouns. + +- `workspace`: As defined by EXWM. +- `frame`: What non-Emacs users would call a "window". Currently my workflow + doesn't use or rely on Emacs frames. +- `window`: A vertical or horizontal split within an Emacs frame. +- `buffer`: Anything storing text in memory. + +## Prefixes and their meanings + +TODO: Have a system for leader-prefixed KBDs, chords, and prefixed chords. + +- `s-`: Switching between named workspaces. Right now, super is too overloaded + and would benefit from having more deliberate keybindings. +- `C-M-`: Window sizing +- `M-{h,j,k,l}`: Window traversing +- `M-{\,-}`: Window splitting +- `M-q`: Window deletion +- `-q`: Window deletion diff --git a/users/wpcarro/emacs/snippets.md b/users/wpcarro/emacs/snippets.md new file mode 100644 index 000000000..2081b5617 --- /dev/null +++ b/users/wpcarro/emacs/snippets.md @@ -0,0 +1,22 @@ +# Snippets + +Specifying snippets that I plan on defining for most of the programming +languages with which I work. I hope this will serve as a checklist of language +constructs I should support when adopting a new language. + +## Shared language features + +These are language features that should be available across most of the +languages that I'm hoping to support. + +- `ld`: anonymous functions (i.e. lambdas) +- `fn`: named function definition +- `var`: variable definition + +## Miscellaneous other language KBDs + +Some of this is related to language tool must-haves, which may need to be a +separate document. + +- `d`: Show documentation +- `x`: Evaluate expression (works mostly for LISPs) diff --git a/users/wpcarro/go/.envrc b/users/wpcarro/go/.envrc new file mode 100644 index 000000000..a4a62da52 --- /dev/null +++ b/users/wpcarro/go/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/go/actors.go b/users/wpcarro/go/actors.go new file mode 100644 index 000000000..1409db185 --- /dev/null +++ b/users/wpcarro/go/actors.go @@ -0,0 +1,45 @@ +package main + +import ( + "bufio" + "fmt" + "os" +) + +// Call function `f` in a go-routine, passing a reference to a newly created +// channel, `c`, as its only argument. Return a reference to `c` to the caller +// of `act`. When `f` halts, close the channel. +func act(f func(chan interface{})) chan interface{} { + c := make(chan interface{}) + + go func() { + defer close(c) + f(c) + }() + + return c +} + +func prompt(msg string) string { + reader := bufio.NewReader(os.Stdin) + fmt.Print(msg) + text, _ := reader.ReadString('\n') + // TODO: Trim trailing newline from the rhs of text. + return text +} + +func main() { + c := act(func(c chan interface{}) { + for { + x := <-c + fmt.Printf("[A] Received value: %v\n", x) + + } + }) + + for { + x := prompt("[B] Enter a value: ") + c <- x + } + os.Exit(0) +} diff --git a/users/wpcarro/go/atomic-counters.go b/users/wpcarro/go/atomic-counters.go new file mode 100644 index 000000000..6cbcd2ee4 --- /dev/null +++ b/users/wpcarro/go/atomic-counters.go @@ -0,0 +1,26 @@ +// Attempting to apply some of the lessons I learned here: +// https://gobyexample.com/atomic-counters +package main + +import ( + "fmt" + "sync" + "sync/atomic" +) + +func main() { + var count uint64 + var wg sync.WaitGroup + + for i := 0; i < 50; i += 1 { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < 1000; j += 1 { + atomic.AddUint64(&count, 1) + } + }() + } + wg.Wait() + fmt.Println("Count: ", count) +} diff --git a/users/wpcarro/go/channels.go b/users/wpcarro/go/channels.go new file mode 100644 index 000000000..cba8abfc9 --- /dev/null +++ b/users/wpcarro/go/channels.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "sync/atomic" +) + +type readMsg struct { + key int + sender chan int +} + +type writeMsg struct { + key int + value int + sender chan bool +} + +func main() { + fmt.Println("Hello, go.") + + var readOps uint64 + var writeOps uint64 + var wg sync.WaitGroup + + reads := make(chan readMsg) + writes := make(chan writeMsg) + + go func() { + state := make(map[int]int) + for { + select { + case msg := <-reads: + msg.sender <- state[msg.key] + case msg := <-writes: + state[msg.key] = msg.value + msg.sender <- true + } + } + }() + + // Reads + for i := 0; i < 100; i += 1 { + go func() { + wg.Add(1) + defer wg.Done() + for j := 0; j < 100; j += 1 { + msg := readMsg{ + key: rand.Intn(5), + sender: make(chan int)} + reads <- msg + val := <-msg.sender + fmt.Printf("Received %d.\n", val) + atomic.AddUint64(&readOps, 1) + } + }() + } + + // Writes + for i := 0; i < 100; i += 1 { + go func() { + wg.Add(1) + defer wg.Done() + for j := 0; j < 100; j += 1 { + msg := writeMsg{ + key: rand.Intn(5), + value: rand.Intn(10), + sender: make(chan bool)} + writes <- msg + <-msg.sender + fmt.Printf("Set %d as %d in state\n", msg.key, msg.value) + atomic.AddUint64(&writeOps, 1) + } + }() + } + + wg.Wait() + fmt.Printf("Read ops: %d\tWrite ops: %d\n", atomic.LoadUint64(&readOps), atomic.LoadUint64(&writeOps)) +} diff --git a/users/wpcarro/go/mutex.go b/users/wpcarro/go/mutex.go new file mode 100644 index 000000000..5cea20754 --- /dev/null +++ b/users/wpcarro/go/mutex.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" + "sync/atomic" + "time" +) + +func main() { + state := make(map[int]int) + mux := &sync.Mutex{} + + var readOps uint64 + var writeOps uint64 + + // Read from state + for i := 0; i < 1000; i += 1 { + for j := 0; j < 100; j += 1 { + go func() { + key := rand.Intn(5) + mux.Lock() + fmt.Printf("state[%d] = %d\n", key, state[key]) + mux.Unlock() + atomic.AddUint64(&readOps, 1) + time.Sleep(time.Millisecond) + }() + } + } + + // Write to state + for i := 0; i < 10; i += 1 { + for j := 0; j < 100; j += 1 { + go func() { + key := rand.Intn(5) + mux.Lock() + state[key] += 1 + mux.Unlock() + fmt.Printf("Wrote to state[%d].\n", key) + atomic.AddUint64(&writeOps, 1) + time.Sleep(time.Millisecond) + }() + } + } + + time.Sleep(time.Millisecond) + + mux.Lock() + fmt.Printf("State: %v\n", state) + mux.Unlock() + fmt.Printf("Reads: %d\tWrites: %d\n", atomic.LoadUint64(&readOps), atomic.LoadUint64(&writeOps)) +} diff --git a/users/wpcarro/go/shell.nix b/users/wpcarro/go/shell.nix new file mode 100644 index 000000000..e14bffae4 --- /dev/null +++ b/users/wpcarro/go/shell.nix @@ -0,0 +1,10 @@ +let + briefcase = import {}; + pkgs = briefcase.third_party.pkgs; +in pkgs.mkShell { + buildInputs = with pkgs; [ + go + goimports + godef + ]; +} diff --git a/users/wpcarro/go/waitgroups.go b/users/wpcarro/go/waitgroups.go new file mode 100644 index 000000000..816321b87 --- /dev/null +++ b/users/wpcarro/go/waitgroups.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "sync" + "time" +) + +func saySomething(x string, wg *sync.WaitGroup) { + defer wg.Done() + fmt.Println(x) + time.Sleep(time.Second) + fmt.Printf("Finished saying \"%s\"\n", x) +} + +func main() { + var wg sync.WaitGroup + var things = [5]string{"chicken", "panini", "cheeseburger", "rice", "bread"} + for i := 0; i < 5; i += 1 { + wg.Add(1) + go saySomething(things[i], &wg) + } + wg.Wait() +} diff --git a/users/wpcarro/gopkgs/kv/default.nix b/users/wpcarro/gopkgs/kv/default.nix new file mode 100644 index 000000000..c89e00213 --- /dev/null +++ b/users/wpcarro/gopkgs/kv/default.nix @@ -0,0 +1,8 @@ +{ depot, ... }: + +depot.buildGo.package { + name = "kv"; + srcs = [ + ./kv.go + ]; +} diff --git a/users/wpcarro/gopkgs/kv/kv.go b/users/wpcarro/gopkgs/kv/kv.go new file mode 100644 index 000000000..040cc63e0 --- /dev/null +++ b/users/wpcarro/gopkgs/kv/kv.go @@ -0,0 +1,39 @@ +// Supporting reading and writing key-value pairs to disk. +package kv + +import ( + "encoding/json" + "io/ioutil" + "log" + "path" +) + +// Return the decoded store from disk. +func getStore(storePath string) map[string]interface{} { + b, err := ioutil.ReadFile(path.Join(storePath, "kv.json")) + if err != nil { + log.Fatal("Could not read store: ", err) + } + var state map[string]interface{} + err = json.Unmarshal(b, &state) + if err != nil { + log.Fatal("Could not decode store as JSON: ", err) + } + return state +} + +// Set `key` to `value` in the store. +func Set(storePath string, key string, value interface{}) error { + state := getStore(storePath) + state[key] = value + b, err := json.Marshal(state) + if err != nil { + log.Fatal("Could not encode state as JSON: ", err) + } + return ioutil.WriteFile(path.Join(storePath, "kv.json"), b, 0644) +} + +// Get `key` from the store. +func Get(storePath string, key string) interface{} { + return getStore(path.Join(storePath, "kv.json"))[key] +} diff --git a/users/wpcarro/gopkgs/utils/default.nix b/users/wpcarro/gopkgs/utils/default.nix new file mode 100644 index 000000000..5955fa4cf --- /dev/null +++ b/users/wpcarro/gopkgs/utils/default.nix @@ -0,0 +1,8 @@ +{ depot, ... }: + +depot.buildGo.package { + name = "utils"; + srcs = [ + ./utils.go + ]; +} diff --git a/users/wpcarro/gopkgs/utils/utils.go b/users/wpcarro/gopkgs/utils/utils.go new file mode 100644 index 000000000..7d662d086 --- /dev/null +++ b/users/wpcarro/gopkgs/utils/utils.go @@ -0,0 +1,131 @@ +// Some utility functions to tidy up my Golang. +package utils + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "net/http/httputil" + "os" + "os/user" + "path/filepath" +) + +// Return the absolute path to the current uesr's home directory. +func HomeDir() string { + user, err := user.Current() + if err != nil { + log.Fatal(err) + } + return user.HomeDir +} + +// Returns true if `info` is a symlink. +func IsSymlink(info os.FileMode) bool { + return info&os.ModeSymlink != 0 +} + +// Return true if `path` exists and false otherwise. +func FileExists(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } else { + return true + } +} + +// Return the absolute file path of `file` using the following resolution +// strategy: +// - Traverse and search upwards until you reach the user's home directory +// - Return the first path in `backupPaths` that exists +// - Fail +func Resolve(fileName string, backupPaths []string) string { + // TODO(wpcarro): Drop hardcoding when whoami behaves as expected. + boundary := "/home" + cwd := "." + files, _ := ioutil.ReadDir(cwd) + + for { + fullCwd, _ := filepath.Abs(cwd) + if fullCwd == boundary { + break + } + for _, file := range files { + if file.Name() == fileName { + path, _ := filepath.Abs(cwd + "/" + file.Name()) + return path + } + } + cwd += "/.." + files, _ = ioutil.ReadDir(cwd) + } + + // TODO(wpcarro): Support expanding these paths to allow the consumer to + // pass in relative paths, and paths with "~" in them. + for _, backup := range backupPaths { + if FileExists(backup) { + return backup + } + } + log.Fatal("Cannot find a run.json to use.") + // This code should be unreachable. + return "" +} + +// Call log.Fatal with `err` when it's not nil. +func FailOn(err error) { + if err != nil { + log.Fatal(err) + } +} + +// Prints the verbose form of an HTTP request. +func DebugRequest(req *http.Request) { + bytes, _ := httputil.DumpRequest(req, true) + fmt.Println(string(bytes)) +} + +// Prints out the verbose form of an HTTP response. +func DebugResponse(res *http.Response) { + bytes, _ := httputil.DumpResponse(res, true) + fmt.Println(string(bytes)) +} + +// Make a simple GET request to `url`. Fail if anything returns an error. I'd +// like to accumulate a library of these, so that I can write scrappy Go +// quickly. For now, this function just returns the body of the response back as +// a string. +func SimpleGet(url string, headers map[string]string, debug bool) string { + client := &http.Client{} + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Fatal(err) + } + for k, v := range headers { + req.Header.Add(k, v) + } + + res, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + defer res.Body.Close() + + if debug { + DebugRequest(req) + DebugResponse(res) + } + + if res.StatusCode == http.StatusOK { + bytes, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Fatal(err) + } + return string(bytes) + } else { + log.Println(res) + log.Fatalf("HTTP status code of response not OK: %v\n", res.StatusCode) + return "" + } +} diff --git a/users/wpcarro/haskell-file/.envrc b/users/wpcarro/haskell-file/.envrc new file mode 100644 index 000000000..a4a62da52 --- /dev/null +++ b/users/wpcarro/haskell-file/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/haskell-file/README.md b/users/wpcarro/haskell-file/README.md new file mode 100644 index 000000000..3f3ac1474 --- /dev/null +++ b/users/wpcarro/haskell-file/README.md @@ -0,0 +1,7 @@ +# haskell-file + +This is a half-baked project. I'd like to write a library whose API closely +resembles some of the more modern filesystem APIs to which I am accustomed: +notably f.el for Elisp. + +I expect more development to come. diff --git a/users/wpcarro/haskell-file/f-todo.org b/users/wpcarro/haskell-file/f-todo.org new file mode 100644 index 000000000..6dd43a962 --- /dev/null +++ b/users/wpcarro/haskell-file/f-todo.org @@ -0,0 +1,67 @@ +* Paths +** TODO f-join (&rest args) +** TODO f-split (path) +** TODO f-expand (path &optional dir) +** TODO f-filename (path) +** TODO f-dirname (path) +** TODO f-common-parent (paths) +** TODO f-ext (path) +** TODO f-no-ext (path) +** TODO f-swap-ext (path ext) +** TODO f-base (path) +** TODO f-relative (path &optional dir) +** TODO f-short (path) +** TODO f-long (path) +** TODO f-canonical (path) +** TODO f-slash (path) +** TODO f-full (path) +** TODO f-uniquify (paths) +** TODO f-uniquify-alist (paths) +* I/O +** TODO f-read-bytes (path) +** TODO f-write-bytes (data path) +** TODO f-read-text (path &optional coding) +** TODO f-write-text(text coding path) +** TODO f-append-text(text coding path) +** TODO f-append-bytes(text coding path) +** TODO Destructive +** TODO f-mkdir (&rest dirs) +** TODO f-delete (path &optional force) +** TODO f-symlink (source path) +** TODO f-move (from to) +** TODO f-copy (from to) +** TODO f-copy-contenst (from to) +** TODO f-touch (path) +** TODO Predicates +** TODO f-exists? (path) +** TODO f-directory? (path) +** TODO f-file? (path) +** TODO f-symlink? (path) +** TODO f-readable? (path) +** TODO f-writable? (path) +** TODO f-executable? (path) +** TODO f-absolute? (path) +** TODO f-relative? (path) +** TODO f-root? (path) +** TODO f-ext? (path ext) +** TODO f-same? (path-a path-b) +** TODO f-parent-of? (path-a path-b) +** TODO f-child-of? (path-a path-b) +** TODO f-ancestor-of? (path-a path-b) +** TODO f-descendant-of? (path-a path-b) +** TODO f-hidden? (path) +** TODO f-empty? (path) +** TODO Stats +** TODO f-size (path) +** f-depth (path) + +* Misc +** TODO f-this-file () +** TODO f-path-separator () +** TODO f-glob (pattern &optional path) +** TODO f-entries (path &optional fn recursive) +** TODO f-directories (path &optional fn recursive) +** TODO f-files (path &optional fn recursive) +** TODO f-root () +** TODO f-traverse-upwards (fn &optional path) +** TODO f-with-sandbox (path-or-paths &rest body) diff --git a/users/wpcarro/haskell-file/f.hs b/users/wpcarro/haskell-file/f.hs new file mode 100644 index 000000000..295575f3f --- /dev/null +++ b/users/wpcarro/haskell-file/f.hs @@ -0,0 +1,64 @@ +module F + ( join + , split + ) where + +-------------------------------------------------------------------------------- +-- Dependencies +-------------------------------------------------------------------------------- + +import Data.List (span) +import System.FilePath (FilePath, pathSeparator) +import System.FilePath.Posix (FilePath) +import qualified System.FilePath.Posix as F + +-- TODO: Move this to a misc.hs, prelude.hs, operators.hs; somewhere. +(|>) :: a -> (a -> b) -> b +(|>) a f = f a +infixl 1 |> + +-- TODO: Move this to a test_utils.hs or elsewhere. +simpleAssert :: (Eq a) => a -> a -> () +simpleAssert x y = + if x == y then + () + else + error "Assertion error" + +-------------------------------------------------------------------------------- +-- Library +-------------------------------------------------------------------------------- + +join :: [FilePath] -> FilePath +join = F.joinPath + +-- | Split path and return list containing parts. +split :: FilePath -> [String] +split = splitJoin . span (/= pathSeparator) + where + splitJoin :: (String, String) -> [String] + splitJoin ([], []) = [] + splitJoin (a, []) = [a] + splitJoin (a, [_]) = [a] + splitJoin (a, _:b) = a : split b + +-------------------------------------------------------------------------------- +-- Tests +-------------------------------------------------------------------------------- + +expected :: [([FilePath], FilePath)] +expected = [ (["path"], "path") + , (["/path"], "/path") + , (["path", "to", "file"], "path/to/file") + , (["/path", "to", "file"], "/path/to/file") + , (["/"], "/") + ] + +runTests :: [()] +runTests = + fmap (\(input, expected) -> simpleAssert (join input) expected) expected + +main :: IO () +main = do + print runTests + pure () diff --git a/users/wpcarro/haskell-file/shell.nix b/users/wpcarro/haskell-file/shell.nix new file mode 100644 index 000000000..4d5b412a0 --- /dev/null +++ b/users/wpcarro/haskell-file/shell.nix @@ -0,0 +1,5 @@ +let + briefcase = import {}; +in briefcase.buildHaskell.shell { + deps = hpkgs: []; +} diff --git a/users/wpcarro/haskell-file/tests.hs b/users/wpcarro/haskell-file/tests.hs new file mode 100644 index 000000000..e3967b77d --- /dev/null +++ b/users/wpcarro/haskell-file/tests.hs @@ -0,0 +1,39 @@ +module FTest where +-------------------------------------------------------------------------------- +import Test.Tasty +import Test.Tasty.Hedgehog +import Hedgehog +-------------------------------------------------------------------------------- +import qualified Hedgehog as H +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range +-------------------------------------------------------------------------------- +import Data.List (intercalate) +import System.FilePath (pathSeparator) +-------------------------------------------------------------------------------- +import F +-------------------------------------------------------------------------------- +main :: IO () +main + = defaultMain + . localOption (HedgehogTestLimit $ Just 50) + $ testGroup "f functions" + [ test_split + ] +-------------------------------------------------------------------------------- +test_split :: TestTree +test_split + = testGroup "split function" + [ testProperty "splits parts properly" splitSuccess + ] +splitSuccess :: Property +splitSuccess = property $ do + -- separator + -- <- H.forAll + -- $ Gen.element ['/', '\\'] + parts + <- H.forAll + . Gen.list (Range.linear 0 10) + $ Gen.list (Range.linear 1 10) Gen.alphaNum + let path = intercalate [pathSeparator] parts + F.split path === parts diff --git a/users/wpcarro/lisp/README.md b/users/wpcarro/lisp/README.md new file mode 100644 index 000000000..9f8693fa6 --- /dev/null +++ b/users/wpcarro/lisp/README.md @@ -0,0 +1,16 @@ +# Common Lisp + +Things that I like about Common Lisp: +- It's an S-expression based language. +- It has a powerful macro system +- It has a unique way of handling-errors +- It is highly introspectible +- The tooling integration with Emacs is the best I have ever seen for any language + +Things that I don't like about Common Lisp: +- I find its standard libraries difficult to use and -- compared to modern + libraries -- like Golang's or Elixir's standard libraries, Common Lisp's + libraries are clunky + +As such, I would like to modernize CL's libraries to resemble other libraries +with which I am more familiar and, therefore, productive. diff --git a/users/wpcarro/lisp/f/README.md b/users/wpcarro/lisp/f/README.md new file mode 100644 index 000000000..34e07180d --- /dev/null +++ b/users/wpcarro/lisp/f/README.md @@ -0,0 +1,5 @@ +# f.lisp + +In this project, I'm attempting to port the Elisp library [`f.el`][1] to Common Lisp. + +[1]: https://github.com/rejeep/f.el diff --git a/users/wpcarro/lisp/f/default.nix b/users/wpcarro/lisp/f/default.nix new file mode 100644 index 000000000..f64bfcc5f --- /dev/null +++ b/users/wpcarro/lisp/f/default.nix @@ -0,0 +1,11 @@ +{ depot, briefcase, ... }: + +depot.nix.buildLisp.library { + name = "f"; + deps = with briefcase.lisp; [ + prelude + ]; + srcs = [ + ./main.lisp + ]; +} diff --git a/users/wpcarro/lisp/f/main.lisp b/users/wpcarro/lisp/f/main.lisp new file mode 100644 index 000000000..a51c38127 --- /dev/null +++ b/users/wpcarro/lisp/f/main.lisp @@ -0,0 +1,48 @@ +(in-package #:cl-user) +(defpackage #:main + (:documentation "Modern API for working with files and directories.") + (:use #:cl) + (:shadow #:type)) +(in-package #:main) + +;; Common Lisp distinguishes between `namestrings` and `pathnames` as two types +;; of filename representations. +;; +;; A `pathname` is a structured representation of the name of a file, which +;; consists of six parts: +;; 1. host +;; 2. device +;; 3. directory +;; 4. name +;; 5. type +;; 6. version + +;; TODO: Should I be using `string` as a type or `namestring`? + +(defmacro type (name in out) + `(declaim (ftype (function ,in ,out) ,name))) + +(type join (&rest namestring) pathname) +(defun join (&rest args) + "Join ARGS to a single path." + (apply #'merge-pathnames args)) + +(type ext (pathname) string) +(defun ext (path) + "Return the file extension of PATH." + (pathname-type path)) + +;; TODO: Define these tests elsewhere. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; join +(string= (join "path") "path") +(string= (join "path" "to") "path/to") +(string= (join "/" "path" "to" "heaven") "/path/to/heaven") + +;; ext +(string= (ext #p"path/to/file.ext") "ext") +(string= (ext #p"path/to/directory") nil) diff --git a/users/wpcarro/lisp/prelude.lisp b/users/wpcarro/lisp/prelude.lisp new file mode 100644 index 000000000..3522567ea --- /dev/null +++ b/users/wpcarro/lisp/prelude.lisp @@ -0,0 +1,14 @@ +(in-package #:cl-user) +(defpackage #:prelude + (:documentation "Supporting miscellaneous utility functions and macros.") + (:use #:cl) + (:shadow #:type) + (:export #:type #:comment)) +(in-package #:prelude) + +;; TODO: Add documentation to these macros. + +(defmacro type (name in out) + `(declaim (ftype (function ,in ,out) ,name))) + +(defmacro comment (&rest _forms) nil) diff --git a/users/wpcarro/lisp/prelude.nix b/users/wpcarro/lisp/prelude.nix new file mode 100644 index 000000000..5fe5d628e --- /dev/null +++ b/users/wpcarro/lisp/prelude.nix @@ -0,0 +1,8 @@ +{ depot, ... }: + +depot.nix.buildLisp.library { + name = "prelude"; + srcs = [ + ./prelude.lisp + ]; +} diff --git a/users/wpcarro/nixos/installer.nix b/users/wpcarro/nixos/installer.nix new file mode 100644 index 000000000..0dff8ea07 --- /dev/null +++ b/users/wpcarro/nixos/installer.nix @@ -0,0 +1,12 @@ +# This expression can be used to create NixOS .iso images. +{ ... }: + +{ + imports = [ + + ]; + config = { + networking.wireless.enable = true; + networking.wireless.networks."GoogleGuest" = {}; + }; +} diff --git a/users/wpcarro/nixos/socrates/default.nix b/users/wpcarro/nixos/socrates/default.nix new file mode 100644 index 000000000..8b762a56d --- /dev/null +++ b/users/wpcarro/nixos/socrates/default.nix @@ -0,0 +1,218 @@ +let + briefcase = import {}; + pkgs = briefcase.third_party.pkgs; +in { + imports = [ ./hardware.nix ]; + + # Use the systemd-boot EFI boot loader. + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + networking = { + hostName = "socrates"; + # The global useDHCP flag is deprecated, therefore explicitly set to false + # here. Per-interface useDHCP will be mandatory in the future, so this + # generated config replicates the default behaviour. + useDHCP = false; + networkmanager.enable = true; + interfaces.enp2s0f1.useDHCP = true; + interfaces.wlp3s0.useDHCP = true; + firewall.allowedTCPPorts = [ 9418 80 443 6697 ]; + }; + + time.timeZone = "UTC"; + + programs.fish.enable = true; + programs.mosh.enable = true; + + environment.systemPackages = with pkgs; [ + curl + direnv + emacs26-nox + gnupg + htop + pass + vim + certbot + tree + git + ]; + + users = { + # I need a git group to run the git server. + groups.git = {}; + + users.wpcarro = { + isNormalUser = true; + extraGroups = [ "git" "wheel" ]; + shell = pkgs.fish; + }; + + users.git = { + group = "git"; + isNormalUser = false; + }; + }; + + nix = { + nixPath = []; + trustedUsers = [ "root" "wpcarro" ]; + }; + + ############################################################################## + # Services + ############################################################################## + + systemd.services.bitlbee-stunnel = { + description = "Provides TLS termination for Bitlbee."; + wantedBy = [ "multi-user.target" ]; + unitConfig = { + Restart = "always"; + User = "nginx"; # This is a hack to easily get certificate access. + }; + script = let configFile = builtins.toFile "stunnel.conf" '' + foreground = yes + debug = 7 + + [ircs] + accept = 0.0.0.0:6697 + connect = 6667 + cert = /var/lib/acme/wpcarro.dev/full.pem + ''; in "${pkgs.stunnel}/bin/stunnel ${configFile}"; + }; + + nixpkgs.config.bitlbee.enableLibPurple = true; + services.bitlbee = { + interface = "0.0.0.0"; + enable = true; + libpurple_plugins = [ + pkgs.telegram-purple + ]; + }; + + services.journaldriver = { + enable = true; + logStream = "home"; + googleCloudProject = "wpcarros-infrastructure"; + applicationCredentials = "/etc/gcp/key.json"; + }; + + services.openssh.enable = true; + + services.gitea = { + enable = true; + # Without this the links to clone a repository like briefcase will be + # "http://localhost:3000/wpcarro/briefcase". + rootUrl = "https://git.wpcarro.dev/"; + }; + + services.buildkite-agents = { + socrates = { + enable = true; + tokenPath = "/etc/secrets/buildkite-agent-token"; + privateSshKeyPath = "/etc/ssh/buildkite_agent_id_rsa"; + }; + }; + + systemd.services.zoo = { + enable = true; + description = "Run my monoserver"; + script = "${briefcase.zoo}/zoo"; + environment = {}; + serviceConfig = { + Restart = "always"; + }; + }; + + services.gitDaemon = { + enable = true; + basePath = "/srv/git"; + exportAll = true; + repositories = [ "/srv/git/briefcase" ]; + }; + + # Since I'm using this laptop as a server in my flat, I'd prefer to close its + # lid. + services.logind.lidSwitch = "ignore"; + + security.polkit.extraConfig = '' + polkit.addRule(function(action, subject) { + polkit.log("subject.user: " + subject.user + " is attempting action.id: " + action.id); + }); + ''; + + # Provision SSL certificates to support HTTPS connections. + security.acme.acceptTerms = true; + security.acme.email = "wpcarro@gmail.com"; + + services.nginx = { + enable = true; + enableReload = true; + + recommendedTlsSettings = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + + commonHttpConfig = '' + log_format json_combined escape=json + '{' + '"remote_addr":"$remote_addr",' + '"method":"$request_method",' + '"host":"$host",' + '"uri":"$request_uri",' + '"status":$status,' + '"request_size":$request_length,' + '"response_size":$body_bytes_sent,' + '"response_time":$request_time,' + '"referrer":"$http_referer",' + '"user_agent":"$http_user_agent"' + '}'; + + access_log syslog:server=unix:/dev/log,nohostname json_combined; + ''; + + virtualHosts = { + "wpcarro.dev" = { + addSSL = true; + enableACME = true; + root = briefcase.website; + }; + "learn.wpcarro.dev" = { + addSSL = true; + enableACME = true; + root = briefcase.website.learn; + }; + "git.wpcarro.dev" = { + addSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://localhost:3000"; + }; + }; + "blog.wpcarro.dev" = { + addSSL = true; + enableACME = true; + root = briefcase.website.blog; + }; + # "sandbox.wpcarro.dev" = { + # addSSL = true; + # enableACME = true; + # root = briefcase.website.sandbox; + # }; + # "learnpianochords.app" = { + # addSSL = true; + # enableACME = true; + # root = briefcase.website.sandbox.learnpianochords; + # }; + "zoo.wpcarro.dev" = { + addSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://localhost:8000"; + }; + }; + }; + }; + + system.stateVersion = "20.09"; +} diff --git a/users/wpcarro/nixos/socrates/hardware.nix b/users/wpcarro/nixos/socrates/hardware.nix new file mode 100644 index 000000000..dde14eb1e --- /dev/null +++ b/users/wpcarro/nixos/socrates/hardware.nix @@ -0,0 +1,30 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, ... }: + +{ + imports = + [ + ]; + + boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "usbhid" "usb_storage" "sd_mod" "rtsx_pci_sdmmc" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/aadf1a77-1e98-4b5f-8e74-abf8e77bda34"; + fsType = "ext4"; + }; + + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/1613-35B9"; + fsType = "vfat"; + }; + + swapDevices = [ ]; + + nix.maxJobs = lib.mkDefault 2; + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; +} diff --git a/users/wpcarro/playbooks/README.md b/users/wpcarro/playbooks/README.md new file mode 100644 index 000000000..70a26c8e8 --- /dev/null +++ b/users/wpcarro/playbooks/README.md @@ -0,0 +1,3 @@ +# playbooks + +Here's the vision: playbooks for everything - not just software. diff --git a/users/wpcarro/playbooks/first-of-the-month.org b/users/wpcarro/playbooks/first-of-the-month.org new file mode 100644 index 000000000..98d6d591f --- /dev/null +++ b/users/wpcarro/playbooks/first-of-the-month.org @@ -0,0 +1,13 @@ +# In total this should take one hour to complete. This is a substantial amount +# of time, which may disincentivize me from completing it. This time is +# amortized over the length of its usefulness (i.e. an entire month), so it +# should be thought of instead as two-minutes worth of work per day that is all +# being completed upfront. +* Tasks +** TODO [20m] Create habit template in journal. +*** Spend time choosing a habit that you can accomplish giving known traveling constraints. +** TODO [45m] Assess previous month's performance. +** TODO [10m] Book massage for the month. +** TODO [05m] Register for HotPodYoga classes. +** TODO [10m] Plan one museum date in London. +** TODO [20m] Plan each weekend for month. diff --git a/users/wpcarro/playbooks/habits.org b/users/wpcarro/playbooks/habits.org new file mode 100644 index 000000000..3b6f6f680 --- /dev/null +++ b/users/wpcarro/playbooks/habits.org @@ -0,0 +1,54 @@ +* First of the year +** [1hr] Write a post mortem for the previous year +* First of the month +** [20m] Create habit template in journal. +** [45m] Assess previous month's performance. +** [10m] Book massage for the month. +** [05m] Register for HotPodYoga classes. +** [10m] Plan one museum date in London. +** [20m] Plan each weekend for month. +* Payday +** [10m] Audit Monzo expenses +** [05m] Review "finances_2020" spreadsheet +** [05m] Transfer GBP to USD account +** [10m] Withdraw cash from ATM +* Morning +** [00m] Wake up at 7:00 +** [15m] Read +** [02m] Brush teeth +** [01m] Make bed +** [01m] Water plants +** [10m] 12 rounds of forward folds +** [05m] 12 rounds Pranayama +** [30m] Transcendental meditation +** [10m] Shower +** [05m] Put on clothes +* Evening +** [01m] Layout tomorrow's outfit +** [01m] Floss +** [02m] Brush teeth +** [01m] Mouth wash +** [30m] Read +** [01m] Journal daily progress +* Monday +** [1hr] Jiu Jitsu +* Tuesday +** Work from 6PS +** [1hr] Jiu Jitsu +* Wednesday +** [1hr] Hot Yoga +** [10m] Shave +** [15m] Clean apartment sinks +* Thursday +* Friday +** [1hr] Hot Yoga +* Saturday +** [10m] Vacuum +** [30m] Nap +* Sunday +** [1hr] Jiu Jitsu +** [30m] Nap +** [10m] Shave +** [05m] Trim nails +** [05m] Take out trash +** [05m] Laundry diff --git a/users/wpcarro/playbooks/hip_opening_challenge/poses.pdf b/users/wpcarro/playbooks/hip_opening_challenge/poses.pdf new file mode 100644 index 000000000..d292ef832 Binary files /dev/null and b/users/wpcarro/playbooks/hip_opening_challenge/poses.pdf differ diff --git a/users/wpcarro/playbooks/hip_opening_challenge/progress.org b/users/wpcarro/playbooks/hip_opening_challenge/progress.org new file mode 100644 index 000000000..80749a3c6 --- /dev/null +++ b/users/wpcarro/playbooks/hip_opening_challenge/progress.org @@ -0,0 +1,65 @@ +# From Lucas Rockwood's 21-day hip challenge from yogabody.com +* DONE day 1 +** pigeon +** butterfly +* DONE day 2 +** blaster +** squat +* DONE day 3 +** happy baby +** thread the needle (supine) +* DONE day 4 +** frog +** jackknife blaster +* DONE day 5 +** lightning bolt +** scissors +* DONE day 6 +** zorro +** supine butterfly (w/ strap) +* TODO day 7 +** thread the needle (wall) +** prone butterfly +* TODO day 8 +** ninja squat +** chair scissors +** lateral chain stretch +* TODO day 9 +** psoas blaster (chair) +** reclined scissors +* DONE day 10 +** twisted blaster +** twisted squat +* TODO day 11 +** double pigeon +** bound butterfly +* TODO day 12 +** eagle fold +** cross-thread +* DONE day 13 +** swiss army knife +** saddle +* TODO day 14 +** butterfly squat +** half lightning bolt +* DONE day 15 +** fallen blaster +** asymmetric baby +* DONE day 16 +** standing psoas +** standing pigeon +* TODO day 17 +** marichi B +** long butterfly +* TODO day 18 +** eagle legs +** chair squat +* DONE day 19 +** twisted pigeon +** bound baby +* DONE day 20 +** seated pigeon +** railroad squat +* DONE day 21 +** thunderbolt +** yogi squat diff --git a/users/wpcarro/playbooks/nix_gcr/README.md b/users/wpcarro/playbooks/nix_gcr/README.md new file mode 100644 index 000000000..9d111cf6b --- /dev/null +++ b/users/wpcarro/playbooks/nix_gcr/README.md @@ -0,0 +1,62 @@ +# Nix + Google Cloud Run (i.e. GCR) + +I'm documenting how I currently deploy projects that I package with Nix on +Google Cloud Run. + +I'd like to automate this workflow as much as possible, and I intend to do just +that. For now, I'm running things manually until I can design an generalization +that appeals to me. + +## Dependencies +- `nix-build` +- `docker` +- `gcloud` + +## Step-by-step + +1. Use `nix-build` to create our Docker image for Cloud Run. + +```shell +> nix-build ./cloud_run.nix +``` + +This outputs a Docker image at `./result`. + +1. Load the built image (i.e. `./result`) into `docker` so that we can tag it + and push it to the Google Container Registry (i.e. GCR). + +```shell +> sudo docker load <./result +``` + +1. (Optionally) Run the image locally to verify its integrity. + +```shell +> sudo docker run -d -p 8080:4242 : +``` + +1. Tag and push the image to GCR. + +```shell +> sudo docker tag :