termonad-4.5.0.0/0000755000000000000000000000000007346545000011660 5ustar0000000000000000termonad-4.5.0.0/.nix-helpers/0000755000000000000000000000000007346545000014174 5ustar0000000000000000termonad-4.5.0.0/.nix-helpers/nixops.nix0000644000000000000000000000655507346545000016247 0ustar0000000000000000# This file can be used with `nixops` to create a virtual machine that has # Termonad installed. # # I use this to test out Termonad in a desktop environment with a menubar. # # On my development machine, I use XMonad as a Window Manager, so there are # no Window decorations for any X application. This file creates a VM # with Termonad installed in Gnome 3. This lets you see what Termonad # looks like when it has Window decorations, a title bar, etc. # # A virtual machine can be created based on this file with the following # commands: # # $ nixops create --deployment termonad-test .nix-helpers/nixops.nix # $ nixops deploy --deployment termonad-test # # This should open up a VirtualBox machine and start installing Gnome 3, # Termonad, etc. # # You should be able to login with the username "myuser" and password "foobar". # # When you are done you can destroy the machine and delete the deployment: # # $ nixops destroy --deployment termonad-test # $ nixops delete --deployment termonad-test # { network.description = "Gnome With Termonad"; termonad-machine = { config, pkgs, ...}: { imports = [ ]; deployment = { targetEnv = "virtualbox"; virtualbox = { disks.disk1.size = 20480; headless = false; memorySize = 2024; vcpu = 1; }; }; environment = { systemPackages = let pkgList = with pkgs; [ acpi aspell aspellDicts.en autojump bash bash-completion bc chromium curl dmenu emacs evince file firefoxWrapper gcc geeqie gimp gitAndTools.gitFull gitAndTools.hub gnumake gnupg hexchat htop imagemagick jq k2pdfopt ltrace manpages ncurses nix-bash-completions nixops p7zip pkgconfig psmisc python3 redshift roxterm screen strace tree unzip usbutils vimHugeX wget wirelesstools xfce.terminal xorg.xbacklight xorg.xmodmap xscreensaver xterm zlib ]; termonad = import ../default.nix { }; in [ termonad ] ++ pkgList; variables.EDITOR = "vim"; }; fonts.fonts = with pkgs; [ dejavu_fonts ipafont source-code-pro ttf_bitstream_vera ]; i18n = { consoleFont = "Lat2-Terminus16"; consoleKeyMap = "us"; defaultLocale = "en_US.UTF-8"; inputMethod = { enabled = "fcitx"; fcitx.engines = with pkgs.fcitx-engines; [ mozc ]; }; }; programs.bash.enableCompletion = true; services = { xserver = { enable = true; layout = "us"; desktopManager.gnome3.enable = true; }; openssh = { enable = true; forwardX11 = true; challengeResponseAuthentication = true; passwordAuthentication = true; permitRootLogin = "yes"; }; }; security.sudo = { enable = true; extraConfig = '' %wheel ALL=(ALL:ALL) NOPASSWD: ${pkgs.systemd}/bin/poweroff %wheel ALL=(ALL:ALL) NOPASSWD: ${pkgs.systemd}/bin/reboot %wheel ALL=(ALL:ALL) NOPASSWD: ${pkgs.systemd}/bin/systemctl suspend ''; }; users.extraUsers.myuser = { extraGroups = [ "audio" "systemd-journal" "video" "wheel" ]; initialPassword = "foobar"; isNormalUser = true; }; }; } termonad-4.5.0.0/.nix-helpers/nixpkgs.nix0000644000000000000000000000570007346545000016401 0ustar0000000000000000# This file pins the version of nixpkgs to a known good version. The nixpkgs is # imported with an overlay adding Termonad. It is imported from various other # files. { # A path to nixpkgs. This will be imported. If null, use a known-working # nixpkgs version. nixpkgs ? null , # Additional overlays to apply when importing nixpkgs. additionalOverlays ? [] , # String representing a GHC version to use. Normally something like # "ghc865". If null, then use a known-working GHC version. compiler ? null , # Build all the examples bundled with termonad. Normally this is only used # in CI for testing that the examples all still compile. buildExamples ? false , # Enable SIXEL support in VTE. # # Setting this to true builds the VTE library with SIXEL enabled. Note that # this uses a special version of VTE that may still have issues. This is # necessary because VTE doesn't yet support SIXEL by default in their release # versions. See https://github.com/cdepillabout/termonad/pull/221 and # https://github.com/cdepillabout/termonad/pull/219 for more information and # examples of what SIXEL looks like in Termonad. # # Also, see https://gitlab.gnome.org/GNOME/vte/-/issues/253 which seems to be # the upstream VTE issue for the SIXEL implementation. # # TODO: Remove this option when the upstream VTE releases officially support # SIXEL. enableSixelSupport ? false , # This is only used for `termonadShell`. # # If this is `true`, Hoogle will also index the Termonad libraries, # however this will mean the environment will need to be rebuilt every # time the termonad source changes. indexTermonad ? false , # Extra Haskell packages that will be visible by Termonad when it compiles # itself. See ./termonad-with-packages.nix for an example of how to use # this. extraHaskellPackages ? null }: let flake-lock = builtins.fromJSON (builtins.readFile ../flake.lock); nixpkgsSrc = if isNull nixpkgs then builtins.fetchTarball { url = "https://github.com/NixOS/nixpkgs/archive/${flake-lock.nodes.nixpkgs.locked.rev}.tar.gz"; sha256 = flake-lock.nodes.nixpkgs.locked.narHash; } else nixpkgs; haskellPackagesOverlays = import ./overlays.nix; # This overlay sets some of the options use we at development time. This # overlay is basically an easy way to pass options to `./overlays.nix` # without having to do it explicitly. termonadOptionsOverlay = self: super: { termonadCompilerVersion = if isNull compiler then super.termonadCompilerVersion else compiler; termonadBuildExamples = buildExamples; termonadIndexTermonad = indexTermonad; termonadExtraHaskellPackages = if isNull extraHaskellPackages then super.termonadExtraHaskellPackages else extraHaskellPackages; termonadEnableSixelSupport = enableSixelSupport; }; in import nixpkgsSrc { overlays = haskellPackagesOverlays ++ [ termonadOptionsOverlay ] ++ additionalOverlays; } termonad-4.5.0.0/.nix-helpers/overlays.nix0000644000000000000000000001604607346545000016567 0ustar0000000000000000 let # An overlay that adds termonad to all haskell package sets. haskellPackagesOverlay = self: super: { haskell = super.haskell // { packageOverrides = hself: hsuper: super.haskell.packageOverrides hself hsuper // { termonad = let filesToIgnore = [ "default.nix" "flake.nix" "flake.lock" ".git" ".nix-helpers" "result" "shell.nix" ".stack-work" "stack.yaml" "stack-nightly.yaml" ".travis.yml" ]; src = builtins.path { # Naming this path makes sure that people will get the same # hash even if they checkout the termonad repo into a # directory called something else. name = "termonad-src"; path = ./..; filter = path: type: with self.lib; ! elem (baseNameOf path) filesToIgnore && ! any (flip hasPrefix (baseNameOf path)) [ "dist" ".ghc" ]; }; extraCabal2nixOptions = self.lib.optionalString self.termonadBuildExamples "-fbuildexamples"; termonadDrv = hself.callCabal2nixWithOptions "termonad" src extraCabal2nixOptions { # There are Haskell packages called gtk3 and pcre2, which # makes these system dependencies not able to be resolved # correctly. inherit (self) gtk3 pcre2; vte_291 = self.vte; }; in termonadDrv; }; }; vte = if self.termonadEnableSixelSupport then super.vte.overrideAttrs (oldAttrs: { # As of 2022-10-20, VTE from Nixpkgs doesn't have sixel enabled by default. # We enable it here. mesonFlags = oldAttrs.mesonFlags ++ [ "-Dsixel=true" ]; # As of 2022-10-20, the released version of VTE doesn't even include SIXEL # support, because upstream says it still has bugs. See # https://github.com/cdepillabout/termonad/pull/221#discussion_r997222069 # and https://gitlab.gnome.org/GNOME/vte/-/issues/253 for more information. src = self.fetchurl { # This is VTE master as of 2022-10-17. url = "https://github.com/GNOME/vte/archive/8ef3f6b2f8043d28cbc82520eb094f09333b26ae.tar.gz"; sha256 = "sha256-2V3dTTu9EH7sO2NeWWZ7pOurQopV/Ji+muoS6+IMNrA="; }; }) else super.vte; # This defines which compiler version is used to build Termonad. # # Either this, or termonadKnownWorkingHaskellPkgSet can be changed in an overlay # if you want to use a different GHC to build Termonad. termonadCompilerVersion = "ghc92"; # A Haskell package set where we know the GHC version works to compile # Termonad. This is basically just a shortcut so that other Nix files # don't need to figure out the correct compiler version to use when it is # not given by the user. termonadKnownWorkingHaskellPkgSet = self.haskell.packages.${self.termonadCompilerVersion}; # See ./nixpkgs.nix for an explanation of what this does. termonadBuildExamples = false; # See ./nixpkgs.nix for an explanation of what this does. termonadIndexTermonad = false; # See ./nixpkgs.nix for an explanation of what this does. termonadEnableSixelSupport = false; # This is a shell environment for hacking on Termonad with cabal. See the # top-level shell.nix for an explanation. termonadShell = let # Nix-shell environment for hacking on termonad. termonadEnv = self.termonadKnownWorkingHaskellPkgSet.termonad.env; # Build tools that are nice to have. It is okay to get Haskell build tools # from any Haskell package set, since they do not depend on the GHC version # we are using. We get these from the normal haskellPackages pkg set because # then they don't have to be compiled from scratch. convenientNativeBuildTools = [ self.cabal-install self.glade self.haskellPackages.ghcid self.hlint self.termonadKnownWorkingHaskellPkgSet.haskell-language-server ]; in if self.termonadIndexTermonad then termonadEnv.overrideAttrs (oldAttrs: { nativeBuildInputs = let ghcEnvWithTermonad = self.termonadKnownWorkingHaskellPkgSet.ghcWithHoogle (hpkgs: [ hpkgs.termonad ]); in oldAttrs.nativeBuildInputs ++ convenientNativeBuildTools ++ [ ghcEnvWithTermonad ]; # Termonad can't be loaded in `cabal repl` unless GHC knows how to find zlib: # https://discourse.nixos.org/t/shared-libraries-error-with-cabal-repl-in-nix-shell/8921/10 LD_LIBRARY_PATH = self.lib.makeLibraryPath [ self.zlib ]; }) else self.termonadKnownWorkingHaskellPkgSet.shellFor { withHoogle = true; packages = hpkgs: [ hpkgs.termonad ]; nativeBuildInputs = termonadEnv.nativeBuildInputs ++ convenientNativeBuildTools; # Termonad can't be loaded in `cabal repl` unless GHC knows how to find zlib: # https://discourse.nixos.org/t/shared-libraries-error-with-cabal-repl-in-nix-shell/8921/10 LD_LIBRARY_PATH = self.lib.makeLibraryPath [ self.zlib ]; }; # Default Haskell packages that you can use in your Termonad configuration. # This is only used if the user doesn't specify the extraHaskellPackages # option. termonadExtraHaskellPackages = hpkgs: with hpkgs; [ colour lens ]; termonad-with-packages = let # GHC environment that has termonad available, as well as the packages # specified above in extraHaskellPackages. env = self.termonadKnownWorkingHaskellPkgSet.ghcWithPackages (hpkgs: [ hpkgs.termonad ] ++ self.termonadExtraHaskellPackages hpkgs); in self.stdenv.mkDerivation { name = "termonad-with-packages-ghc-${env.version}"; buildInputs = [ self.gdk-pixbuf self.gnome.adwaita-icon-theme self.hicolor-icon-theme ]; nativeBuildInputs = [ self.wrapGAppsHook ]; dontBuild = true; unpackPhase = ":"; # Using installPhase instead of buildCommand was recommended here: # https://github.com/cdepillabout/termonad/pull/109 installPhase = '' runHook preInstall mkdir -p $out/bin ln -sf ${env}/bin/termonad $out/bin/termonad gappsWrapperArgs+=( --set NIX_GHC "${env}/bin/ghc" ) runHook postInstall ''; preferLocalBuild = true; allowSubstitutes = false; }; }; in [ haskellPackagesOverlay ] termonad-4.5.0.0/.nix-helpers/stack-shell.nix0000644000000000000000000000053707346545000017133 0ustar0000000000000000# This is the shell file specified in the stack.yaml file. # This runs stack commands in an environment created with nix. with (import ./nixpkgs.nix {}); haskell.lib.buildStackProject { name = "termonad"; buildInputs = [ cairo git gobject-introspection gtk3 vte zlib ]; ghc = termonadKnownWorkingHaskellPkgSet.ghc; } termonad-4.5.0.0/.nix-helpers/termonad-with-packages.nix0000644000000000000000000000643107346545000021256 0ustar0000000000000000# This file produces a wrapper around Termonad that will know where to find a # GHC with the libraries needed to recompile its config file. # # This is not NixOS only; it should work with nix on any system. # # There are 4 different ways this file can be used. # # 1. build directly with `nix-build` # # This method allows you to build termonad from the command line with # `nix-build`. This is the easiest method. # # You can call `nix-build` like the following: # # $ nix-build .nix-helpers/termonad-with-packages.nix # # (This is the same as just calling `nix-build` on the `../default.nix` file.) # # This produces a `result` directory that contains the `termonad` exectuable as # `result/bin/termonad`. # # By default, you will be able to use the Haskell packages `lens` and `colour` # in your `~/.config/termonad/termonad.hs` configuration file. # # If you want to use alternative Haskell packages, you can specify them on the # command line: # # $ nix-build .nix-helpers/termonad-with-packages.nix \ # --arg extraHaskellPackages 'haskellPackages: [ haskellPackages.colour haskellPackages.lens haskellPackages.pipes ]' # # This will make sure you can also use the Haskell packages `lens`, `colour`, and `pipes` in your # `~/.config/termonad/termonad.hs` file. (Actually, if Termonad transitively # depends on a library, you should be able to use it without having to specify # it. Although you shouldn't depend on this.) # # 2. install with `nix-env` # # `nix-env` can be used to install Termonad into your environment: # # $ nix-env --file .nix-helpers/termonad-with-packages.nix --install # # If you want to specify extra Haskell packages that you can use in your # `~/.config/termonad/termonad.hs` file, you can call `nix-env` like the # following: # # $ nix-env --file .nix-helpers/termonad-with-packages.nix --install \ # --arg extraHaskellPackages 'haskellPackages: [ haskellPackages.colour haskellPackages.lens haskellPackages.pipes ]' # # 3. an overlay for your user # # Nix makes it easy to create overlays. First, create the following file at # `~/.config/nixpkgs/overlays/termonad.nix`: # # ```nix # self: super: # let extraHaskellPackages = hp: [ hp.colour hp.lens hp.MonadRandom ]; in # { termonad = import /some/path/to/termonad/.nix-helpers/termonad-with-packages.nix { inherit extraHaskellPackages; }; # } # ``` # # Now Termonad can be installed through Nix's standard methods, including `nix-env`: # # $ nix-env --file '' --install --attr termonad # # 4. system-wide in /etc/nixos/configuration.nix # # You can set the `nixpkgs.overlays` attribute in your # `/etc/nixos/configuration.nix` file just like in (3) above. # # ```nix # { config, pkgs, ... }: # { # nixpkgs.overlays = [ (import /home/youruser/.config/nixpkgs/overlays/termonad.nix) ]; # } # ``` # # You can also add Termonad directly to your system packages: # # ```nix # { config, pkgs, ... }: # { # environment.systemPackages = with pkgs; [ # ... # (import /some/path/to/termonad/.nix-helpers/termonad-with-packages.nix { }) # ... # ]; # } # ``` { extraHaskellPackages ? null , nixpkgs ? null , additionalOverlays ? [] , compiler ? null , buildExamples ? false }@args: let pkgs = import ./nixpkgs.nix { inherit compiler nixpkgs additionalOverlays buildExamples extraHaskellPackages; }; in pkgs.termonad-with-packages termonad-4.5.0.0/CHANGELOG.md0000644000000000000000000003144107346545000013474 0ustar0000000000000000## 4.5.0.0 * Add an `allowBold` option (which defaults to `True`). This can be used if you want disable use of bold text. Thanks [@zanculmarktum](https://github.com/zanculmarktum)! [#225](https://github.com/cdepillabout/termonad/pull/225) ## 4.4.0.0 * Add support for opening URLs in a browser by right-clicking on them. URLs will also become underlined if you mouse-over them. [#222](https://github.com/cdepillabout/termonad/pull/222) ## 4.3.0.0 * Add SIXEL support. Note that you will need to set `enableSixel` to `True` in your `ConfigOptions`. In order for `enableSixel` to have any affect, you'll need to use version of VTE that is >= 0.63, and has been compiled with SIXEL support. There is also a report that even if you enable SIXEL and have a supported version of VTE, there may still be some problems. See the linked PR for more information, including how to compile VTE with SIXEL support. Thanks [@junjihashimoto](https://github.com/junjihashimoto)! [#219](https://github.com/cdepillabout/termonad/pull/219) ## 4.2.0.1 * Added an [example](https://github.com/cdepillabout/termonad/blob/50b20dc3f5bb2a4a080fd703e818bf721c9c3884/example-config/ExampleDraculaColourExtension.hs) of how to setup a Dracula color scheme. Thanks @craigem! [#195](https://github.com/cdepillabout/termonad/pull/195) * Bump to allow both aeson-1 and aeson-2. Thanks [@gelisam](https://github.com/gelisam)! [#210](https://github.com/cdepillabout/termonad/pull/210) ## 4.2.0.0 * Add new options `highlightFgColour` and `highlightBgColour` for setting the color of highlighted text [#190](https://github.com/cdepillabout/termonad/pull/190). Thanks [@zanculmarktum](https://github.com/zanculmarktum)! * Termonad creates a configuration file in `~/.config/termonad/termonad.yaml` for use with the Preferences editor. This is only used if you don't have a `termonad.hs` file. The configuration file loading code has been updated to be more robust in loading configurations that are missing fields. This means that if you update Termonad from an old version, your preferences will still be able to be loaded in most cases [#191](https://github.com/cdepillabout/termonad/pull/191). Thanks again [@jecaro](https://github.com/jecaro)! * Added an [example](https://github.com/cdepillabout/termonad/blob/74d04ba1469184cd8667f88d24dfbc7d50d7f658/example-config/ExamplePaperColourColourExtension.hs) of how to setup a PaperColour color scheme. Thanks @craigem! [#193](https://github.com/cdepillabout/termonad/pull/193) ## 4.1.1.0 * Add new shortcuts to switch to the next and previous tab: CtrlPgDown and CtrlPgUp. This works similar to gnome-terminal and xfce4-terminal. [#180](https://github.com/cdepillabout/termonad/pull/180). Thanks [@juliendehos](https://github.com/juliendehos)! ## 4.1.0.0 * Add an option for enabling "bold is bright". This forces colors from the extended light palette to be used whenever Termonad prints bold text. [#178](https://github.com/cdepillabout/termonad/pull/178). Thanks [@M0M097](https://github.com/M0M097)! ## 4.0.1.2 * Disable doctest test-suite when building with GHC-8.10.3. The doctests appear to be segfaulting, but only when compiled with GHC-8.10.3. [#175](https://github.com/cdepillabout/termonad/pull/175). ## 4.0.1.1 * Bump upper dependency on `base` so that Termonad is compatible with GHC-8.10. [#172](https://github.com/cdepillabout/termonad/pull/172). Thanks [@mimi1vx](https://github.com/mimi1vx)! ## 4.0.1.0 * Add Preferences link to context menu. This is a convenient way to open the Preferences if you don't have the menu shown by default. [#171](https://github.com/cdepillabout/termonad/pull/171) Thanks [@maridonkers](https://github.com/maridonkers)! ## 4.0.0.1 * Update Termonad to be able to be built with the latest versions of the haskell-gi libraries. This shouldn't affect most users building with `stack`. It is only used [currently](https://github.com/NixOS/nixpkgs/pull/95434) for building Termonad with packages from Nixpkgs. ## 4.0.0.0 * Remove the dependently typed code for specifying terminal colors. [#161](https://github.com/cdepillabout/termonad/pull/161). Thanks [@ssbothwell](https://github.com/ssbothwell)! The `Palette` data type has been updated to not used length-indexed lists, but instead just newtype wrappers around normal lists. In prevous versions, the `Palette` data type looked like this: ```haskell data Palette c = NoPalette | BasicPalette !(Vec N8 c) | ExtendedPalette !(Vec N8 c) !(Vec N8 c) | ColourCubePalette !(Vec N8 c) !(Vec N8 c) !(Matrix '[N6, N6, N6] c) | FullPalette !(Vec N8 c) !(Vec N8 c) !(Matrix '[N6, N6, N6] c) !(Vec N24 c) ``` In 4.0.0.0, `Palette` has been changed to the following: ```haskell data Palette c = NoPalette | BasicPalette !(List8 c) | ExtendedPalette !(List8 c) !(List8 c) | ColourCubePalette !(List8 c) !(List8 c) !(Matrix c) | FullPalette !(List8 c) !(List8 c) !(Matrix c) !(List24 c) ``` Instead of using types like `Vec N8 c`, you will use types like `List8 c`. When setting the `palette` field of in a `ColourConfig`, you can now do it like the following. Note that there is both a `mkList8` function that returns `Maybe`, and an `unsafeMkList8` that throws a runtime error. Most users will probably want to use the `unsafeMkList8` function, since it is easy to use, and you can eyeball whether the list has the correct number of elements. If you're doing something more complicated, you may want to use the `mkList8` function: ```haskell myColourConfig :: ColourConfig (AlphaColour Double) myColourConfig = defaultColourConfig { palette = ExtendedPalette myStandardColours (maybe defaultLightColours id myLightColours) } where -- This is a an example of creating a linked-list of colours, -- This function uses an unsafe method for generating the list. -- An exception will be thrown if your list does not have exactly 8 elements. myStandardColours :: List8 (AlphaColour Double) myStandardColours = unsafeMkList8 [ createColour 40 30 20 -- dark brown (used as background colour) , createColour 180 30 20 -- red , createColour 40 160 20 -- green , createColour 180 160 20 -- dark yellow , createColour 40 30 120 -- dark purple , createColour 180 30 120 -- bright pink , createColour 40 160 120 -- teal , createColour 180 160 120 -- light brown ] -- This is an example of creating a linked-list of colours with a type -- safe method. mkList8 produces a Maybe value which must be handled explicitely. myLightColours :: Maybe (List8 (AlphaColour Double)) myLightColours = mkList8 [ createColour 70 60 50 -- brown , createColour 220 30 20 -- light red , createColour 40 210 20 -- light green , createColour 220 200 20 -- yellow , createColour 40 30 180 -- purple , createColour 140 30 80 -- dark pink , createColour 50 200 160 -- light teal , createColour 220 200 150 -- light brown ] ``` Also see the functions `setAtList8`, `overAtList8`, `setAtList24`, `overAtList24`, etc. ## 3.1.0.1 * Correct the solarized colours [#148](https://github.com/cdepillabout/termonad/pull/148). Thanks [@craigem](https://github.com/craigem)! * Add an example showing Gruvbox colours [#149](https://github.com/cdepillabout/termonad/pull/149). Thanks again [@craigem](https://github.com/craigem)! * Set an upperbound on `base` so we make sure that only GHC-8.8 is used. Some of the dependencies of Termonad don't support GHC-8.10 yet. ## 3.1.0.0 * Fix up deprecated functions used in Setup.hs. This should allow Termonad to be compiled with Cabal-3.0.0.0 (which is used by default in GHC-8.8). [#144](https://github.com/cdepillabout/termonad/pull/144) Thanks [mdorman](https://github.com/mdorman)! * Fully update to LTS-15 and GHC-8.8. Termonad now requires GHC-8.8 in order to be compiled. [#145](https://github.com/cdepillabout/termonad/pull/145). ## 3.0.0.0 * Remove the one-pixel white border around the `GtkNotebook` (the GTK widget thing that contains the tabs). [#138](https://github.com/cdepillabout/termonad/pull/138) * Add a right-click menu for the terminal. It currently allows copy and paste. [#136](https://github.com/cdepillabout/termonad/pull/136) Thanks @jecaro! * Add a preferences file that settings will be saved to and read from at `~/.config/termonad/termonad.yaml`. You can change settings with the Preferences dialog. **The settings will only be used from this file if you do not have a `~/.config/termonad/termonad.hs` file**. [#140](https://github.com/cdepillabout/termonad/pull/140) Thanks again @jecaro! ## 2.1.0.0 * Add a menu option to set preferences for a running Termonad session. The preferences you have set are lost when you end the Termonad session. [#130](https://github.com/cdepillabout/termonad/pull/130) Thanks @jecaro! ## 2.0.0.0 * Added menu option to search for a regex within the terminal output. This removes support for versions of VTE-2.91 older than 0.46. This means that compiling on older versions of Debian and Ubuntu may no longer work. [#118](https://github.com/cdepillabout/termonad/pull/118) ## 1.3.0.0 * Change all uses of [`Colour`](http://hackage.haskell.org/package/colour-2.3.5/docs/Data-Colour.html#t:Colour) to [`AlphaColour`](http://hackage.haskell.org/package/colour-2.3.5/docs/Data-Colour.html#t:AlphaColour) in `Termonad.Config.Colour`. Users should now use `AlphaColour` instead of `Colour`. Also, all uses of `sRGB24` should be replaced with `createColour`. This change is mechanical and should not affect how Termonad works at all. Thanks to @jecaro and @amir! [#116](https://github.com/cdepillabout/termonad/pull/116) ## 1.2.0.0 * Got the code for setting the backgroud color of the terminal actually working. Thanks @dakotaclemenceplaza. [#111](https://github.com/cdepillabout/termonad/pull/111) * This changes the type of `ColourConfig` to make the foreground and background colors of the terminal optional. * Various cleanup in the nix files. ## 1.1.0.0 * Added an [example](https://github.com/cdepillabout/termonad/blob/0cd741d51958806092418b55abdf1c1dc078841c/example-config/ExampleSolarizedColourExtension.hs) of how to setup a solarized color scheme. Thanks @craigem. [#90](https://github.com/cdepillabout/termonad/pull/90) and [#103](https://github.com/cdepillabout/termonad/pull/103) * Various fixes in the nix files. Make sure Termonad can see the GTK icons. [#91](https://github.com/cdepillabout/termonad/pull/91) and [#92](https://github.com/cdepillabout/termonad/pull/92) * Add a menu option to change the font size at runtime. You should be able to do this with the `Ctrl-+` and `Ctrl--` keys. [#95](https://github.com/cdepillabout/termonad/pull/95) * Get building with GHC 8.6. Thanks @clinty. [#98](https://github.com/cdepillabout/termonad/pull/98) ## 1.0.1.0 * Stop using the `widgetSetFocusOnClick` function, which is not supported on older versions of GTK. This lets Termonad be compiled with older versions of GTK. [#87](https://github.com/cdepillabout/termonad/pull/87). * Add CI. [#87](https://github.com/cdepillabout/termonad/pull/87). * Support versions of VTE-2.91 older than 0.44. [#87](https://github.com/cdepillabout/termonad/pull/87). * Add some functions for converting from a list to a `Vec` in `Termonad.Config.Vec`: `fromListVec` and `fromListVec_`. Commit 883eb98b5f. * Fix the paste hotkey. [#86](https://github.com/cdepillabout/termonad/pull/86). ## 1.0.0.0 * The API for configuring Termonad is now completely different. Many, many changes have gone into this version. You should approach it as a completely different application. The CHANGELOG will be kept up-to-date for future releases. ## 0.2.1.0 * Make sure the window title is set to "Termonad". * Relabel tabs when termonad is started. ## 0.2.0.0 * Open dialog asking if you want to quit when you try to use your WM to quit. * Termonad will attempt to open up a new terminal in the working directory of the current terminal. * Make sure termonad won't crash if dyre can't find GHC. * Add a few more ways to compile on NixOS. * Add an icon for termonad. ## 0.1.0.0 * Initial release. termonad-4.5.0.0/LICENSE0000644000000000000000000000277407346545000012677 0ustar0000000000000000Copyright Dennis Gosnell (c) 2017-2021 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 Author name here nor the names of other 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. termonad-4.5.0.0/README.md0000644000000000000000000005233707346545000013151 0ustar0000000000000000 Termonad ========= [![CI](https://github.com/cdepillabout/termonad/actions/workflows/ci.yml/badge.svg)](https://github.com/cdepillabout/termonad/actions/workflows/ci.yml) [![Hackage](https://img.shields.io/hackage/v/termonad.svg)](https://hackage.haskell.org/package/termonad) [![Stackage LTS](http://stackage.org/package/termonad/badge/lts)](http://stackage.org/lts/package/termonad) [![Stackage Nightly](http://stackage.org/package/termonad/badge/nightly)](http://stackage.org/nightly/package/termonad) [![BSD3 license](https://img.shields.io/badge/license-BSD3-blue.svg)](./LICENSE) [![Join the chat at https://gitter.im/termonad/Lobby](https://badges.gitter.im/termonad/Lobby.svg)](https://gitter.im/termonad/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Termonad is a terminal emulator configurable in Haskell. It is extremely customizable and provides hooks to modify the default behavior. It can be thought of as the "XMonad" of terminal emulators. ![image of Termonad](./img/termonad.png) Termonad was [featured on an episode](https://www.youtube.com/watch?v=TLNr_gBv5HY) of [DistroTube](https://www.youtube.com/channel/UCVls1GmFKf6WlTraIb_IaJg). This video gives a short overview of Termonad. **Table of Contents** - [Termonad](#termonad) - [Installation](#installation) - [Arch Linux](#arch-linux) - [Ubuntu / Debian](#ubuntu--debian) - [Nix](#nix) - [Mac OS X](#mac-os-x) - [Installing with just `stack`](#installing-with-just-stack) - [Installing with just `nix`](#installing-with-just-nix) - [Installing with `stack` using `nix`](#installing-with-stack-using-nix) - [Windows](#windows) - [How to use Termonad](#how-to-use-termonad) - [Default Key Bindings](#default-key-bindings) - [Configuring Termonad](#configuring-termonad) - [Compiling Local Settings](#compiling-local-settings) - [Running with `stack`](#running-with-stack) - [Running with `nix`](#running-with-nix) - [Goals](#goals) - [Where to get help](#where-to-get-help) - [Contributions](#contributions) - [Maintainers](#maintainers) ## Installation Termonad can be installed on any system as long as the necessary GTK libraries are available. The following are instructions for installing Termonad on a few different distributions and systems. If the given steps don't work for you, or you want to add instructions for an additional system, please send a pull request. The following steps use the [`stack`](https://docs.haskellstack.org/en/stable/README/) build tool to build Termonad, but [`cabal`](https://www.haskell.org/cabal/) can be used as well. Steps for installing `stack` can be found on [this page](https://docs.haskellstack.org/en/stable/install_and_upgrade/). ### Arch Linux First, you must install the required Gnome/GTK system libraries: ```sh $ pacman -S vte3 gobject-introspection ``` In order to install Termonad, clone this repository and run `stack install`. This will install the `termonad` binary to `~/.local/bin/`: ```sh $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ stack install ``` Note that Termonad depends on the [`haskell-gi`](https://github.com/haskell-gi/haskell-gi) family of Haskell libraries. `haskell-gi` contains Haskell wrappers for for Gnome/GTK system libraries. It uses the [GObject Introspection](https://gi.readthedocs.io/en/latest/) functionality from the Gnome libraries. One problem that Arch users often run into is that their system Gnome/GTK libraries are newer than what the `haskell-gi` dependencies from Stackage support. If you run into this problem, there are a couple things you can try: - Manually switch to a newer Stackage resolver (probably Stackage Nightly). Newer Stackage resolvers often have newer versions of the `haskell-gi` libraries. Newer versions of the `haskell-gi` libraries are more likely to support your newer system Gnome/GTK libraries. If you get something working like this, please open a PR. - Use `cabal` for building Termonad instead of `stack`. Make sure `cabal`'s constraint solver picks the latest versions of the `haskell-gi` libraries on Hackage. - Use Nix for installing Termonad. My suggestion is to use Nix, since it is highly likely to "just work" (because with Nix, _all_ libraries are pinned to known working versions, even system libraries). ### Ubuntu / Debian Termonad can be installed through `apt` on Debian and Ubuntu: ```console $ sudo apt install termonad libghc-termonad-dev ``` Note that the `libghc-termonad-dev` package is necessary if you want to be able to compile the Haskell-based settings file, `termonad.hs`. #### Compiling from source on Ubuntu / Debian First, you must install the required Gnome/GTK system libraries: ```sh $ apt-get install gobject-introspection libgirepository1.0-dev libgtk-3-dev libvte-2.91-dev libpcre2-dev ``` In order to install Termonad, clone this repository and run `stack install`. This will install the `termonad` binary to `~/.local/bin/`: ```sh $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ stack install ``` ### Nix If you have `nix` installed, you should be able to use it to obtain Termonad. This means that it will work on NixOS, or with `nix` on another distro. There are three different ways to use `nix` to get Termonad: 1. Get Termonad from Nixpkgs. Termonad is provided as a top-level `termonad` attribute in Nixpkgs. For instance, run a `nix-shell` with Termonad: ```console $ nix-shell -p termonad $ termonad # run termonad within the nix-shell ``` You can also install `termonad` with tools like `nix-env` or home-manager. If you're using NixOS, you can add `termonad` to your `environment.systemPackages` list. Keep in mind that if you're using an old release of NixOS, you'll likely get an older version of Termonad. 2. Build Termonad using the code in this repository. The following commands clone this repo and build the `termonad` binary at `./result/bin/`: ```sh $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ nix-build ``` 3. Build Termonad using `stack` with Nix-integration. The following commands install `stack` for your user, clone this repository, and install the `termonad` binary to `~/.local/bin/`: ```sh $ nix-env -i stack $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ stack --nix install ``` (_edit_: Building with `stack` using Nix-integration does not currently work. See [#99](https://github.com/cdepillabout/termonad/issues/99).) ### Mac OS X Building and installing Termonad on Mac OS X should be possible with any of the following three methods: - Install the required system libraries (like GTK and VTE) by hand, then use `stack` to build Termonad. This is probably the easiest method. You don't have to understand anything about `nix`. However, it is slightly annoying to have to install GTK and VTE by hand. - Use `nix` to install both the required system libraries and Termonad itself. If you are a nix user and want an easy way to install Termonad, this is the recommended method. - Use `nix` to install install the required system libraries, and `stack` to build Termonad. If you are a nix user, but want to use `stack` to actually do development on Termonad, using `stack` may be easier than using `cabal`. The following sections describe each method. #### Installing with just `stack` (*currently no instructions available. please send a PR adding instructions if you get termonad to build using this method.*) #### Installing with just `nix` `nix` can be used to install Termonad with the following steps, assuming you have `nix` [installed](https://nixos.org/nix/download.html). These commands clone this repository and build the `termonad` binary at `./result/bin/`: ```sh $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ nix-build ``` #### Installing with `stack` using `nix` `stack` can be used in conjunction with `nix` to install Termonad. `nix` will handle installing system dependencies (like GTK and VTE), while `stack` will handle compiling and installing Haskell packages. You must have `nix` [installed](https://nixos.org/nix/download.html). You will also need `stack` installed. You can do that with the following command: ```sh $ nix-env -i stack ``` After `stack` is installed, you will need to clone Termonad and build it: ``` $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ stack --nix install ``` This will install the `termonad` binary to `~/.local/bin/`. ### Windows To run Termonad on Windows, you'll need: * any X server app, for example **Vcxsrv** * any WSL, for example **Ubuntu** I'm using both Vcxsrv and Ubuntu WSL. Configure both Vcxsrv and WSL. For Vcxsrv go with default settings everywhere, it will be fine. Configure your WSL as you want (choose your name etc.). After you set up the user, you'll have to update your OS, run: ```console $ sudo apt-get update $ sudo apt-get upgrade -y $ sudo apt-get dist-upgrade -y $ sudo apt-get autoremove -y ``` Configure the `DISPLAY` environment variable for the X server, and load the changes in bash: For WSL1: ```console $ echo "export DISPLAY=localhost:0.0" >> ~/.bashrc $ source ~/.bashrc ``` For WSL2: ```console $ echo export DISPLAY=$(awk '/nameserver / {print $2; exit}' /etc/resolv.conf 2>/dev/null):0 >> ~/.bashrc $ echo export LIBGL_ALWAYS_INDIRECT=1 >> ~/.bashrc $ source ~/.bashrc ``` If you're using WSL2, you have to create a separate **inbound rule** for TCP port 6000, to allow WSL access to the X server. If you're using mentioned earlier **Vcxsrv** you can enable public access for your X server by disabling Access Control on the Extra Settings. You can also use `-ac` flag in the Additional parameters for VcXsrv section. Your X server should now be configured. Execute following command to install the necessary GTK system libraries: ```console $ apt-get install gobject-introspection libgirepository1.0-dev libgtk-3-dev libvte-2.91-dev libpcre2-dev ``` The required GTK system libraries should now be installed. Clone the Termonad repo: ```sh $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ stack build $ stack run ``` After `stack run`, you should see a new window with your Termonad running. ## How to use Termonad Termonad is similar to XMonad. The above steps will install a `termonad` binary somewhere on your system. If you have installed Termonad using `stack`, the `termonad` binary will be in `~/.local/bin/`. This binary is a version of Termonad configured with default settings. You can try running it to get an idea of what Termonad is like: ```sh $ ~/.local/bin/termonad ``` The following section describes the default key bindings. If you would like to configure Termonad with your own settings, first you will need to create a Haskell file called `~/.config/termonad/termonad.hs`. A following section gives an example configuration file. If this configuration file exists, when the `~/.local/bin/termonad` binary launches, it will try to use GHC to compile the configuration file. If GHC is able to successfully compile the configuration file, a separate binary will be created called something like `~/.cache/termonad/termonad-linux-x86_64`. This binary file can be thought of as your own personal Termonad, configured with all your own settings. When you run `~/.local/bin/termonad`, it will re-exec `~/.cache/termonad/termonad-linux-x86_64` if it exists. However, there is one difficulty with this setup. In order for the `~/.local/bin/termonad` binary to be able to compile your `~/.config/termonad/termonad.hs` configuration file, Termonad needs to know where GHC is, as well as where all your Haskell packages live. This presents some difficulties that will be discussed in a following section. ### Default Key Bindings Termonad provides the following default key bindings. | Key binding | Action | |------------|--------| | Ctrl Shift t | Open new tab. | | Ctrl Shift w | Close tab. | | Ctrl Shift f | Open Find dialog for searching for a regex. | | Ctrl Shift p | Find the regex **above** the current position. | | Ctrl Shift i | Find the regex **below** the current position. | | Ctrl + | Increase font size. | | Ctrl - | Decrease font size. | | Ctrl PgUp | Switch to previous tab. | | Ctrl PgDown | Switch to next tab. | | Alt (number key) | Switch to tab `number`. For example, Alt 2 switches to tab 2. | ### Configuring Termonad Termonad has two different ways to be configured. The first way is to use the built-in Preferences editor. You can find this in the `Preferences` menu under `Edit` in the menubar. When opening Termonad for the first time, it will create a preferences file at `~/.config/termonad/termonad.yaml`. When you change a setting in the Preferences editor, Termonad will update the setting in the preferences file. When running Termonad, it will load settings from the preferences file. Do not edit the preferences file by hand, because it will be overwritten when updating settings in the Preferences editor. This method is perfect for users who only want to make small changes to the Termonad settings, like the default font size. The second way to configure Termonad is to use a Haskell-based settings file, called `~/.config/termonad/termonad.hs` by default. This method allows you to make large, sweeping changes to Termonad. This method is recommended for power users. **WARNING: If you have a `~/.config/termonad/termonad.hs` file, then all settings from `~/.config/termonad/termonad.yaml` will be ignored. If you want to set *ANY* settings in `~/.config/termonad/termonad.hs`, then you must set *ALL* settings in `~/.config/termonad/termonad.hs`.** The following is an example Termonad configuration file. You should save this to `~/.config/termonad/termonad.hs`. You can find more information on the available configuration options within the [`Termonad.Config`](https://hackage.haskell.org/package/termonad/docs/Termonad-Config.html) module. ```haskell {-# LANGUAGE OverloadedStrings #-} module Main where import Termonad.App (defaultMain) import Termonad.Config ( FontConfig, FontSize(FontSizePoints), Option(Set) , ShowScrollbar(ShowScrollbarAlways), defaultConfigOptions, defaultFontConfig , defaultTMConfig, fontConfig, fontFamily, fontSize, options, showScrollbar ) import Termonad.Config.Colour ( AlphaColour, ColourConfig, addColourExtension, createColour , createColourExtension, cursorBgColour, defaultColourConfig ) -- | This sets the color of the cursor in the terminal. -- -- This uses the "Data.Colour" module to define a dark-red color. -- There are many default colors defined in "Data.Colour.Names". cursBgColour :: AlphaColour Double cursBgColour = createColour 204 0 0 -- | This sets the colors used for the terminal. We only specify the background -- color of the cursor. colConf :: ColourConfig (AlphaColour Double) colConf = defaultColourConfig { cursorBgColour = Set cursBgColour } -- | This defines the font for the terminal. fontConf :: FontConfig fontConf = defaultFontConfig { fontFamily = "DejaVu Sans Mono" , fontSize = FontSizePoints 13 } main :: IO () main = do colExt <- createColourExtension colConf let termonadConf = defaultTMConfig { options = defaultConfigOptions { fontConfig = fontConf -- Make sure the scrollbar is always visible. , showScrollbar = ShowScrollbarAlways } } `addColourExtension` colExt defaultMain termonadConf ``` There are other example configuration files in the [example-config/](./example-config) directory. If you want to test what all the colors look like, you may find it convenient to use the [`print-console-colors`](http://hackage.haskell.org/package/print-console-colors) package, which provides an executable called `print-console-colors` that prints all of the colors for your terminal. ### Compiling Local Settings If you launch Termonad by calling `~/.local/bin/termonad`, it will try to compile the `~/.config/termonad/termonad.hs` file if it exists. The problem is that `~/.local/bin/termonad` needs to be able to see GHC and the required Haskell libraries to be able to compile `~/.config/termonad/termonad.hs`. There are a couple solutions to this problem, listed in the sections below. (These steps are definitely confusing. I would love to figure out a better way to do this. Please submit an issue or PR if you have a good idea about how to fix this.) #### Running with `stack` If you originally compiled Termonad with `stack`, you can use `stack` to execute Termonad. First, you must change to the directory with the Termonad source code. From there, you can run `stack exec`: ```sh $ cd termonad/ # change to the termonad source code directory $ stack exec -- termonad ``` `stack` will pick up the correct GHC version and libraries from the `stack.yaml` and `termonad.cabal` file. `termonad` will be run in an environment with GHC available. `termonad` will use this GHC and libraries to compile your `~/.config/termonad/termonad.hs` file. It if succeeds, it should create a `~/.cache/termonad/termonad-linux-x86_64` binary. If you need extra Haskell libraries available when compiling your `~/.config/termonad/termonad.hs` file, you can specify them to `stack exec`: ```sh $ stack exec --package lens --package conduit -- termonad ``` The problem with this is that `stack exec` changes quite a few of your environment variables. It is not recommended to actually run Termonad from within `stack exec`. After you run `stack exec -- termonad` and let it recompile your `~/.config/termonad/termonad.hs` file, exit Termonad. Re-run Termonad by calling it directly. Termonad will notice that `~/.config/termonad/termonad.hs` hasn't changed since `~/.cache/termonad/termonad-linux-x86_64` has been recompiled, so it will directly execute `~/.cache/termonad/termonad-linux-x86_64`. #### Running with `nix` Building Termonad with `nix` (by running `nix-build` in the top directory) sets it up so that Termonad can see GHC. Termonad should be able to compile the `~/.config/termonad/termonad.hs` file by default. If you're interested in how this works, or want to change which Haskell packages are available from your `~/.config/termonad/termonad.hs` file, please see the documentation in the [`.nix-helpers/termonad-with-packages.nix`](./.nix-helpers/termonad-with-packages.nix) file. ## Additional Info This section contains some additional info that may be helpful for using Termonad. ### Opening URLs by right-clicking It is possible to open a URL in a browser by right-clicking on it, and selecting `Open URL in browser`. In order for this you work, you may have to setup your XDG defaults. You can set the default browser to Firefox with a command like the following: ```console $ xdg-mime default firefox.desktop x-scheme-handler/http $ xdg-mime default firefox.desktop x-scheme-handler/https ``` This `xdg-mime` executable comes from a package called `xdg-utils` in both Nixpkgs and Ubutun/Debian. ## Goals Termonad has the following goals: * fully configurable in Haskell There are already [many](https://gnometerminator.blogspot.com/p/introduction.html) [good](https://www.enlightenment.org/about-terminology.md) [terminal](http://software.schmorp.de/pkg/rxvt-unicode.html) [emulators](https://launchpad.net/sakura). However, there are no terminal emulators fully configurable in Haskell. Termonad fills this niche. * flexible Most people only need a terminal emulator that lets you change the font-size, cursor color, etc. They don't need tons of configuration options. Termonad should be for people that like lots of configuration options. Termonad should provide many hooks to allow the user full control over its behavior. * stable Termonad should be able to be used everyday as your main terminal emulator. It should not crash for any reason. If you experience a crash, please file an issue or a pull request! * good documentation The [documentation](https://hackage.haskell.org/package/termonad) for Termonad on Hackage should be good. You shouldn't have to guess at what certain data types or functions do. If you have a hard time understanding anything in the documentation, please submit an issue or PR. ## Where to get help If you find a bug in Termonad, please either [send a PR](https://github.com/cdepillabout/termonad/pulls) fixing it or create an [issue](https://github.com/cdepillabout/termonad/issues) explaining it. If you just need help with configuring Termonad, you can either join the [Gitter room](https://gitter.im/termonad/Lobby) or [#termonad on irc.freenode.net](https://webchat.freenode.net/). ## Contributions Contributions are highly appreciated. Termonad is currently missing many helpful configuration options and behavior hooks. If there is something you would like to add, please submit an issue or PR. ## Maintainers - [cdepillabout](https://github.com/cdepillabout) termonad-4.5.0.0/Setup.hs0000644000000000000000000001225707346545000013323 0ustar0000000000000000-- This file comes from cabal-doctest: -- https://github.com/phadej/cabal-doctest/blob/master/simple-example -- -- It is needed so that doctest can be run with the same options as the modules -- are compiled with. {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedLists #-} {-# OPTIONS_GHC -Wall #-} module Main (main) where import Data.Maybe (catMaybes) import Distribution.PackageDescription (HookedBuildInfo, cppOptions, emptyBuildInfo) import Distribution.Simple (UserHooks, defaultMainWithHooks, preBuild, preRepl, simpleUserHooks) import Distribution.Simple.Program (configureProgram, defaultProgramDb, getDbProgramOutput, pkgConfigProgram) import Distribution.Text (simpleParse) import Distribution.Verbosity (normal) import Distribution.Version (Version, mkVersion) #ifndef MIN_VERSION_cabal_doctest #define MIN_VERSION_cabal_doctest(x,y,z) 0 #endif #if MIN_VERSION_cabal_doctest(1,0,0) import Distribution.Extra.Doctest (addDoctestsUserHook) main :: IO () main = do cppOpts <- getTermonadCPPOpts defaultMainWithHooks . addPkgConfigTermonadUserHook cppOpts $ addDoctestsUserHook "doctests" simpleUserHooks #else #ifdef MIN_VERSION_Cabal -- If the macro is defined, we have new cabal-install, -- but for some reason we don't have cabal-doctest in package-db -- -- Probably we are running cabal sdist, when otherwise using new-build -- workflow #warning You are configuring this package without cabal-doctest installed. \ The doctests test-suite will not work as a result. \ To fix this, install cabal-doctest before configuring. #endif main :: IO () main = do cppOpts <- getTermonadCPPOpts defaultMainWithHooks $ addPkgConfigTermonadUserHook cppOpts simpleUserHooks #endif -- | Add CPP macros representing the version of the GTK and VTE system -- libraries. addPkgConfigTermonadUserHook :: [String] -> UserHooks -> UserHooks addPkgConfigTermonadUserHook cppOpts oldUserHooks = do oldUserHooks { preBuild = pkgConfigTermonadHook cppOpts $ preBuild oldUserHooks , preRepl = pkgConfigTermonadHook cppOpts (preRepl oldUserHooks) } pkgConfigTermonadHook :: [String] -> (args -> flags -> IO HookedBuildInfo) -> args -> flags -> IO HookedBuildInfo pkgConfigTermonadHook cppOpts oldFunc args flags = do (maybeOldLibHookedInfo, oldExesHookedInfo) <- oldFunc args flags case maybeOldLibHookedInfo of Just oldLibHookedInfo -> do let newLibHookedInfo = oldLibHookedInfo { cppOptions = cppOptions oldLibHookedInfo <> cppOpts } pure (Just newLibHookedInfo, oldExesHookedInfo) Nothing -> do let newLibHookedInfo = emptyBuildInfo { cppOptions = cppOpts } pure (Just newLibHookedInfo, oldExesHookedInfo) getTermonadCPPOpts :: IO [String] getTermonadCPPOpts = do gtk <- getGtkVersionCPPOpts vte <- getVteVersionCPPOpts pure $ gtk <> vte getVteVersionCPPOpts :: IO [String] getVteVersionCPPOpts = do maybeVers <- getPkgConfigVersionFor "vte-2.91" case maybeVers of Nothing -> pure [] Just vers -> pure $ createVteVersionCPPOpts vers getGtkVersionCPPOpts :: IO [String] getGtkVersionCPPOpts = do maybeVers <- getPkgConfigVersionFor "gtk+-3.0" case maybeVers of Nothing -> pure [] Just vers -> pure $ createGtkVersionCPPOpts vers -- | Just like 'createGtkVersionCPPOpts' but for VTE instead of GTK. createVteVersionCPPOpts :: Version -> [String] createVteVersionCPPOpts vers = catMaybes [ if vers >= mkVersion [0,44] then Just "-DVTE_VERSION_GEQ_0_44" else Nothing , if vers >= mkVersion [0,63] then Just "-DVTE_VERSION_GEQ_0_63" else Nothing ] -- | Based on the version of the GTK3 library as reported by @pkg-config@, return -- a list of CPP macros that contain the GTK version. These can be used in the -- Haskell code to work around differences in the gi-gtk library Haskell -- library when compiled against different versions of the GTK system library. -- -- This list may need to be added to. createGtkVersionCPPOpts :: Version -- ^ 'Version' of the GTK3 library as reported by @pkg-config@. -> [String] -- ^ A list of CPP macros to show the GTK version. createGtkVersionCPPOpts gtkVersion = catMaybes [ if gtkVersion >= mkVersion [3,22] then Just "-DGTK_VERSION_GEQ_3_22" else Nothing ] getPkgConfigVersionFor :: String -> IO (Maybe Version) getPkgConfigVersionFor program = do pkgDb <- configureProgram normal pkgConfigProgram defaultProgramDb pkgConfigOutput <- getDbProgramOutput normal pkgConfigProgram pkgDb ["--modversion", program] -- Drop the newline on the end of the pkgConfigOutput. -- This should give us a version number like @3.22.11@. let rawProgramVersion = reverse $ drop 1 $ reverse pkgConfigOutput let maybeProgramVersion = simpleParse rawProgramVersion case maybeProgramVersion of Nothing -> do putStrLn $ "In Setup.hs, in getPkgConfigVersionFor, could not parse " <> program <> " version: " <> show pkgConfigOutput putStrLn $ "\nNot defining any CPP macros based on the version of the system " <> program <> " library." putStrLn "\nCompilation of termonad may fail." pure Nothing Just programVersion -> pure $ Just programVersion termonad-4.5.0.0/app/0000755000000000000000000000000007346545000012440 5ustar0000000000000000termonad-4.5.0.0/app/Main.hs0000644000000000000000000000025107346545000013656 0ustar0000000000000000 module Main where import Termonad (defaultMain) import Termonad.Config (tmConfigFromPreferencesFile) main :: IO () main = defaultMain =<< tmConfigFromPreferencesFile termonad-4.5.0.0/default.nix0000644000000000000000000000176207346545000014032 0ustar0000000000000000# This is the main nix file for Termonad. It will build the `termonad` # executable. It will place it in a "wrapped" environment so that Termonad can # find GHC and a few Haskell libraries. This wrapped Termonad will be able to # rebuild its configuration file (which should be located at # `~/.config/termonad/termonad.hs`). # # Termonad can be built with the command `nix-build` in the main top # directory. # # The `termonad` executable will be created at `result/bin/termonad`. With # this default setup, you will be able to use the haskell packages `lens` and # `colour` in your `~/.config/termonad/termonad.hs` file, as well as the # Termonad package itself. # # If you want to install termonad into your environment, you can use `nix-env` # from the main top directory: # # $ nix-env --file default.nix --install { nixpkgs ? null , additionalOverlays ? [] , compiler ? null , buildExamples ? false , enableSixelSupport ? false }@args: (import .nix-helpers/nixpkgs.nix args).termonad-with-packages termonad-4.5.0.0/example-config/0000755000000000000000000000000007346545000014556 5ustar0000000000000000termonad-4.5.0.0/example-config/ExampleColourExtension.hs0000644000000000000000000000674407346545000021601 0ustar0000000000000000-- | This is an example Termonad configuration that shows how to use the -- 'ColourExtension' to set colours for your terminal. See the project-wide -- README.md for how to use Termonad Haskell configuration scripts. module Main where import Data.Maybe (fromMaybe) import Termonad ( CursorBlinkMode(CursorBlinkModeOff), Option(Set) , ShowScrollbar(ShowScrollbarNever), TMConfig, confirmExit, cursorBlinkMode , defaultConfigOptions, defaultTMConfig, options, showMenu, showScrollbar , start ) import Termonad.Config.Colour ( AlphaColour, ColourConfig, List8, Palette(ExtendedPalette), addColourExtension , createColour, createColourExtension, cursorBgColour, defaultColourConfig , defaultLightColours, foregroundColour, palette, mkList8, unsafeMkList8 ) -- This is our main 'TMConfig'. It holds all of the non-colour settings -- for Termonad. -- -- This shows how a few settings can be changed. myTMConfig :: TMConfig myTMConfig = defaultTMConfig { options = defaultConfigOptions { showScrollbar = ShowScrollbarNever , confirmExit = False , showMenu = False , cursorBlinkMode = CursorBlinkModeOff } } -- This is our 'ColourConfig'. It holds all of our colour-related settings. myColourConfig :: ColourConfig (AlphaColour Double) myColourConfig = defaultColourConfig -- Set the cursor background colour. This is the normal colour of the -- cursor. { cursorBgColour = Set (createColour 120 80 110) -- purple -- Set the default foreground colour of text of the terminal. , foregroundColour = Set (createColour 220 180 210) -- light pink -- Set the extended palette that has 8 colours standard colors and then 8 -- light colors. , palette = ExtendedPalette myStandardColours (fromMaybe defaultLightColours myLightColours) } where -- This is a an example of creating a linked-list of colours, -- This function uses an unsafe method for generating the list. -- An exception will be thrown if your list does not have 8 elements. myStandardColours :: List8 (AlphaColour Double) myStandardColours = unsafeMkList8 [ createColour 40 30 20 -- dark brown (used as background colour) , createColour 180 30 20 -- red , createColour 40 160 20 -- green , createColour 180 160 20 -- dark yellow , createColour 40 30 120 -- dark purple , createColour 180 30 120 -- bright pink , createColour 40 160 120 -- teal , createColour 180 160 120 -- light brown ] -- This is an example of creating a linked-list of colours with a type -- safe method. mkList8 produces a Maybe value which must be handled explicitely. myLightColours :: Maybe (List8 (AlphaColour Double)) myLightColours = mkList8 [ createColour 70 60 50 -- brown , createColour 220 30 20 -- light red , createColour 40 210 20 -- light green , createColour 220 200 20 -- yellow , createColour 40 30 180 -- purple , createColour 140 30 80 -- dark pink , createColour 50 200 160 -- light teal , createColour 220 200 150 -- light brown ] main :: IO () main = do -- First, create the colour extension based on 'myColourConfig'. myColourExt <- createColourExtension myColourConfig -- Update 'myTMConfig' with our colour extension. let newTMConfig = addColourExtension myTMConfig myColourExt -- Start Termonad with our updated 'TMConfig'. start newTMConfig termonad-4.5.0.0/example-config/ExampleDraculaColourExtension.hs0000644000000000000000000000621007346545000023061 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} -- | This is an example Termonad configuration that shows how to use the -- Dracula colour scheme https://draculatheme.com/ module Main where import Data.Maybe (fromMaybe) import Termonad ( CursorBlinkMode(CursorBlinkModeOn) , Option(Set) , ShowScrollbar(ShowScrollbarNever) , TMConfig , confirmExit , cursorBlinkMode , defaultConfigOptions , defaultTMConfig , options , showMenu , showScrollbar , start , FontConfig , FontSize(FontSizePoints) , defaultFontConfig , fontConfig , fontFamily , fontSize ) import Termonad.Config.Colour ( AlphaColour , ColourConfig , Palette(ExtendedPalette) , addColourExtension , createColour , createColourExtension , defaultColourConfig , defaultStandardColours , defaultLightColours , backgroundColour , foregroundColour , palette , List8 , mkList8 ) -- This is our main 'TMConfig'. It holds all of the non-colour settings -- for Termonad. -- -- This shows how a few settings can be changed. myTMConfig :: TMConfig myTMConfig = defaultTMConfig { options = defaultConfigOptions { showScrollbar = ShowScrollbarNever , confirmExit = False , showMenu = False , cursorBlinkMode = CursorBlinkModeOn , fontConfig = fontConf } } -- This is our Dracula 'ColourConfig'. dracula :: ColourConfig (AlphaColour Double) dracula = defaultColourConfig -- Set the default background & foreground colour of text of the terminal. { backgroundColour = Set (createColour 40 42 54) -- black.0 , foregroundColour = Set (createColour 248 248 242) -- white.7 -- Set the extended palette that has 2 Vecs of 8 Dracula palette colours , palette = ExtendedPalette draculaNormal draculaBright } where draculaNormal :: List8 (AlphaColour Double) draculaNormal = fromMaybe defaultStandardColours $ mkList8 [ createColour 40 42 54 -- black.0 , createColour 255 85 85 -- red.1 , createColour 80 250 123 -- green.2 , createColour 241 250 140 -- yellow.3 , createColour 189 147 249 -- blue.4 , createColour 255 121 198 -- magenta.5 , createColour 139 233 253 -- cyan.6 , createColour 191 191 191 -- white.7 ] draculaBright :: List8 (AlphaColour Double) draculaBright = fromMaybe defaultStandardColours $ mkList8 [ createColour 77 77 77 -- black.8 , createColour 255 110 103 -- red.9 , createColour 90 247 142 -- green.10 , createColour 244 249 157 -- yellow.11 , createColour 202 169 250 -- blue.12 , createColour 255 146 208 -- magenta.13 , createColour 154 237 254 -- cyan.14 , createColour 230 230 230 -- white.15 ] fontConf = defaultFontConfig { fontFamily = "Monospace" , fontSize = FontSizePoints 12 } main :: IO () main = do -- First, create the colour extension based on either PaperColor modules. myColourExt <- createColourExtension dracula -- Update 'myTMConfig' with our colour extension. let newTMConfig = addColourExtension myTMConfig myColourExt -- Start Termonad with our updated 'TMConfig'. start newTMConfig termonad-4.5.0.0/example-config/ExampleGruvboxColourExtension.hs0000644000000000000000000001117707346545000023152 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} -- | This is an example Termonad configuration that shows how to use the -- Gruvbox colour scheme https://github.com/morhetz/gruvbox module Main where import Data.Maybe (fromMaybe) import Termonad ( CursorBlinkMode(CursorBlinkModeOn) , Option(Set) , ShowScrollbar(ShowScrollbarNever) , TMConfig , confirmExit , cursorBlinkMode , defaultConfigOptions , defaultTMConfig , options , showMenu , showScrollbar , start , FontConfig , FontSize(FontSizePoints) , defaultFontConfig , fontConfig , fontFamily , fontSize ) import Termonad.Config.Colour ( AlphaColour , ColourConfig , Palette(ExtendedPalette) , addColourExtension , createColour , createColourExtension , defaultColourConfig , defaultStandardColours , defaultLightColours , backgroundColour , foregroundColour , palette , List8 , mkList8 ) -- This is our main 'TMConfig'. It holds all of the non-colour settings -- for Termonad. -- -- This shows how a few settings can be changed. myTMConfig :: TMConfig myTMConfig = defaultTMConfig { options = defaultConfigOptions { showScrollbar = ShowScrollbarNever , confirmExit = False , showMenu = False , cursorBlinkMode = CursorBlinkModeOn , fontConfig = fontConf } } -- This is our Gruvbox dark 'ColourConfig'. It holds all of our dark-related settings. gruvboxDark :: ColourConfig (AlphaColour Double) gruvboxDark = defaultColourConfig -- Set the default foreground colour of text of the terminal. { foregroundColour = Set (createColour 213 196 161) -- fg2 , backgroundColour = Set (createColour 40 40 40) -- bg0 -- Set the extended palette that has 2 Vecs of 8 Gruvbox palette colours , palette = ExtendedPalette gruvboxDark1 gruvboxDark2 } where gruvboxDark1 :: List8 (AlphaColour Double) gruvboxDark1 = fromMaybe defaultStandardColours $ mkList8 [ createColour 40 40 40 -- bg0 , createColour 204 36 29 -- red.1 , createColour 152 151 26 -- green.2 , createColour 215 153 33 -- yellow.3 , createColour 69 133 136 -- blue.4 , createColour 177 98 134 -- purple.5 , createColour 104 157 106 -- aqua.6 , createColour 189 174 147 -- fg3 ] gruvboxDark2 :: List8 (AlphaColour Double) gruvboxDark2 = fromMaybe defaultStandardColours $ mkList8 [ createColour 124 111 100 -- bg4 , createColour 251 71 52 -- red.9 , createColour 184 187 38 -- green.10 , createColour 250 189 47 -- yellow.11 , createColour 131 165 152 -- blue.12 , createColour 211 134 155 -- purple.13 , createColour 142 192 124 -- aqua.14 , createColour 235 219 178 -- fg1 ] -- This is our Gruvbox light 'ColourConfig'. It holds all of our dark-related settings. gruvboxLight :: ColourConfig (AlphaColour Double) gruvboxLight = defaultColourConfig -- Set the default foreground colour of text of the terminal. { foregroundColour = Set (createColour 60 56 54) -- fg1 , backgroundColour = Set (createColour 251 241 199) -- bg0 -- Set the extended palette that has 2 Vecs of 8 Gruvbox palette colours , palette = ExtendedPalette gruvboxLight1 gruvboxLight2 } where gruvboxLight1 :: List8 (AlphaColour Double) gruvboxLight1 = fromMaybe defaultLightColours $ mkList8 [ createColour 251 241 199 -- bg0 , createColour 204 36 29 -- red.1 , createColour 152 151 26 -- green.2 , createColour 215 153 33 -- yellow.3 , createColour 69 133 136 -- blue.4 , createColour 177 98 134 -- purple.5 , createColour 104 157 106 -- aqua.6 , createColour 102 82 84 -- fg3 ] gruvboxLight2 :: List8 (AlphaColour Double) gruvboxLight2 = fromMaybe defaultLightColours $ mkList8 [ createColour 168 153 132 -- bg4 , createColour 157 0 6 -- red.9 , createColour 121 116 14 -- green.10 , createColour 181 118 20 -- yellow.11 , createColour 7 102 120 -- blue.12 , createColour 143 63 113 -- purple.13 , createColour 66 123 88 -- aqua.14 , createColour 60 56 54 -- fg1 ] -- This defines the font for the terminal. fontConf :: FontConfig fontConf = defaultFontConfig { fontFamily = "Monospace" , fontSize = FontSizePoints 12 } main :: IO () main = do -- First, create the colour extension based on either Gruvboxmodules. myColourExt <- createColourExtension gruvboxDark -- Update 'myTMConfig' with our colour extension. let newTMConfig = addColourExtension myTMConfig myColourExt -- Start Termonad with our updated 'TMConfig'. start newTMConfig termonad-4.5.0.0/example-config/ExampleOneDarkProColourExtension.hs0000644000000000000000000000636407346545000023524 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} -- | This is an example Termonad configuration that shows how to use the -- One Dark Pro colour scheme https://binaryify.github.io/OneDark-Pro/ -- -- See an example in @../img/termonad-One_Dark_Pro.png@. module Main where import Data.Maybe (fromMaybe) import Termonad ( CursorBlinkMode(CursorBlinkModeOn) , Option(Set) , ShowScrollbar(ShowScrollbarNever) , TMConfig , confirmExit , cursorBlinkMode , defaultConfigOptions , defaultTMConfig , options , showMenu , showScrollbar , start , FontConfig , FontSize(FontSizePoints) , defaultFontConfig , fontConfig , fontFamily , fontSize ) import Termonad.Config.Colour ( AlphaColour , ColourConfig , Palette(ExtendedPalette) , addColourExtension , createColour , createColourExtension , defaultColourConfig , defaultStandardColours , defaultLightColours , backgroundColour , foregroundColour , palette , List8 , mkList8 ) -- This is our main 'TMConfig'. It holds all of the non-colour settings -- for Termonad. -- -- This shows how a few settings can be changed. myTMConfig :: TMConfig myTMConfig = defaultTMConfig { options = defaultConfigOptions { showScrollbar = ShowScrollbarNever , confirmExit = False , showMenu = False , cursorBlinkMode = CursorBlinkModeOn , fontConfig = fontConf } } -- This is our Dracula 'ColourConfig'. onedarkpro :: ColourConfig (AlphaColour Double) onedarkpro = defaultColourConfig -- Set the default background & foreground colour of text of the terminal. { backgroundColour = Set (createColour 40 44 52) -- black.0 , foregroundColour = Set (createColour 171 178 191) -- white.7 -- Set the extended palette that has 2 Vecs of 8 Dracula palette colours , palette = ExtendedPalette onedarkproNormal onedarkproBright } where onedarkproNormal :: List8 (AlphaColour Double) onedarkproNormal = fromMaybe defaultStandardColours $ mkList8 [ createColour 40 44 52 -- black.0 , createColour 244 108 117 -- red.1 , createColour 152 195 121 -- green.2 , createColour 229 192 123 -- yellow.3 , createColour 97 175 239 -- blue.4 , createColour 198 120 221 -- magenta.5 , createColour 86 182 194 -- cyan.6 , createColour 171 178 191 -- white.7 ] onedarkproBright :: List8 (AlphaColour Double) onedarkproBright = fromMaybe defaultStandardColours $ mkList8 [ createColour 63 63 63 -- black.8 , createColour 224 108 117 -- red.9 , createColour 152 195 121 -- green.10 , createColour 229 192 123 -- yellow.11 , createColour 97 175 239 -- blue.12 , createColour 198 120 221 -- magenta.13 , createColour 86 182 194 -- cyan.14 , createColour 191 197 206 -- white.15 ] fontConf = defaultFontConfig { fontFamily = "Monospace" , fontSize = FontSizePoints 12 } main :: IO () main = do -- First, create the colour extension based on either PaperColor modules. myColourExt <- createColourExtension onedarkpro -- Update 'myTMConfig' with our colour extension. let newTMConfig = addColourExtension myTMConfig myColourExt -- Start Termonad with our updated 'TMConfig'. start newTMConfig termonad-4.5.0.0/example-config/ExamplePaperColourColourExtension.hs0000644000000000000000000001144507346545000023747 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} -- | This is an example Termonad configuration that shows how to use the -- PaperColor colour scheme https://github.com/NLKNguyen/papercolor-theme module Main where import Data.Maybe (fromMaybe) import Termonad ( CursorBlinkMode(CursorBlinkModeOn) , Option(Set) , ShowScrollbar(ShowScrollbarNever) , TMConfig , confirmExit , cursorBlinkMode , defaultConfigOptions , defaultTMConfig , options , showMenu , showScrollbar , start , FontConfig , FontSize(FontSizePoints) , defaultFontConfig , fontConfig , fontFamily , fontSize ) import Termonad.Config.Colour ( AlphaColour , ColourConfig , Palette(ExtendedPalette) , addColourExtension , createColour , createColourExtension , defaultColourConfig , defaultStandardColours , defaultLightColours , backgroundColour , foregroundColour , palette , List8 , mkList8 ) -- This is our main 'TMConfig'. It holds all of the non-colour settings -- for Termonad. -- -- This shows how a few settings can be changed. myTMConfig :: TMConfig myTMConfig = defaultTMConfig { options = defaultConfigOptions { showScrollbar = ShowScrollbarNever , confirmExit = False , showMenu = False , cursorBlinkMode = CursorBlinkModeOn , fontConfig = fontConf } } -- This is our PaperColor dark 'ColourConfig'. It holds all of our dark-related settings. paperColorDark :: ColourConfig (AlphaColour Double) paperColorDark = defaultColourConfig -- Set the default background & foreground colour of text of the terminal. { backgroundColour = Set (createColour 28 28 28) -- black.0 , foregroundColour = Set (createColour 208 208 208) -- white.7 -- Set the extended palette that has 2 Vecs of 8 PaperColor palette colours , palette = ExtendedPalette paperColorNormal paperColorBright } where paperColorNormal :: List8 (AlphaColour Double) paperColorNormal = fromMaybe defaultStandardColours $ mkList8 [ createColour 28 28 28 -- black.0 , createColour 175 0 95 -- red.1 , createColour 95 175 0 -- green.2 , createColour 215 175 95 -- yellow.3 , createColour 95 175 215 -- blue.4 , createColour 128 128 128 -- purple.5 , createColour 215 135 95 -- aqua.6 , createColour 208 208 208 -- white.7 ] paperColorBright :: List8 (AlphaColour Double) paperColorBright = fromMaybe defaultStandardColours $ mkList8 [ createColour 88 88 88 -- black.8 , createColour 95 175 95 -- red.9 , createColour 175 215 0 -- green.10 , createColour 175 135 215 -- yellow.11 , createColour 255 175 0 -- blue.12 , createColour 255 95 175 -- purple.13 , createColour 0 175 175 -- aqua.14 , createColour 95 135 135 -- white.15 ] -- This is our PaperColor light 'ColourConfig'. It holds all of our light-related settings paperColorLight :: ColourConfig (AlphaColour Double) paperColorLight = defaultColourConfig -- Set the default background & foreground colour of text of the terminal. { backgroundColour = Set (createColour 238 238 238) -- black.0 , foregroundColour = Set (createColour 68 68 68) -- white.7 -- Set the extended palette that has 2 Vecs of 8 PaperColor palette colours , palette = ExtendedPalette paperColorNormal paperColorBright } where paperColorNormal :: List8 (AlphaColour Double) paperColorNormal = fromMaybe defaultLightColours $ mkList8 [ createColour 238 238 238 -- black.0 , createColour 175 0 0 -- red.1 , createColour 0 135 0 -- green.2 , createColour 95 135 0 -- yellow.3 , createColour 0 135 175 -- blue.4 , createColour 135 135 135 -- purple.5 , createColour 0 95 135 -- aqua.6 , createColour 68 68 68 -- white.7 ] paperColorBright :: List8 (AlphaColour Double) paperColorBright = fromMaybe defaultLightColours $ mkList8 [ createColour 188 188 188 -- black.8 , createColour 215 0 0 -- red.9 , createColour 215 0 135 -- green.10 , createColour 135 0 175 -- yellow.11 , createColour 215 95 0 -- blue.12 , createColour 215 95 0 -- purple.13 , createColour 0 95 175 -- aqua.14 , createColour 0 95 135 -- white.15 ] -- This defines the font for the terminal. fontConf :: FontConfig fontConf = defaultFontConfig { fontFamily = "Monospace" , fontSize = FontSizePoints 12 } main :: IO () main = do -- First, create the colour extension based on either PaperColor modules. myColourExt <- createColourExtension paperColorLight -- Update 'myTMConfig' with our colour extension. let newTMConfig = addColourExtension myTMConfig myColourExt -- Start Termonad with our updated 'TMConfig'. start newTMConfig termonad-4.5.0.0/example-config/ExampleSolarizedColourExtension.hs0000644000000000000000000001124407346545000023445 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} -- | This is an example Termonad configuration that shows how to use the -- Solarized colour scheme https://ethanschoonover.com/solarized/ module Main where import Data.Maybe (fromMaybe) import Termonad ( CursorBlinkMode(CursorBlinkModeOn) , Option(Set) , ShowScrollbar(ShowScrollbarNever) , TMConfig , confirmExit , cursorBlinkMode , defaultConfigOptions , defaultTMConfig , options , showMenu , showScrollbar , start , FontConfig , FontSize(FontSizePoints) , defaultFontConfig , fontConfig , fontFamily , fontSize ) import Termonad.Config.Colour ( AlphaColour , ColourConfig , Palette(ExtendedPalette) , addColourExtension , createColour , createColourExtension , defaultColourConfig , defaultStandardColours , defaultLightColours , backgroundColour , foregroundColour , palette , List8 , mkList8 ) -- This is our main 'TMConfig'. It holds all of the non-colour settings -- for Termonad. -- -- This shows how a few settings can be changed. myTMConfig :: TMConfig myTMConfig = defaultTMConfig { options = defaultConfigOptions { showScrollbar = ShowScrollbarNever , confirmExit = False , showMenu = False , cursorBlinkMode = CursorBlinkModeOn , fontConfig = fontConf } } -- This is our Solarized dark 'ColourConfig'. It holds all of our dark-related settings. solarizedDark :: ColourConfig (AlphaColour Double) solarizedDark = defaultColourConfig -- Set the default foreground colour of text of the terminal. { foregroundColour = Set (createColour 131 148 150) -- base0 , backgroundColour = Set (createColour 0 43 54) -- base03 -- Set the extended palette that has 2 Vecs of 8 Solarized palette colours , palette = ExtendedPalette solarizedDark1 solarizedDark2 } where solarizedDark1 :: List8 (AlphaColour Double) solarizedDark1 = fromMaybe defaultStandardColours $ mkList8 [ createColour 7 54 66 -- base02 , createColour 220 50 47 -- red , createColour 133 153 0 -- green , createColour 181 137 0 -- yellow , createColour 38 139 210 -- blue , createColour 211 54 130 -- magenta , createColour 42 161 152 -- cyan , createColour 238 232 213 -- base2 ] solarizedDark2 :: List8 (AlphaColour Double) solarizedDark2 = fromMaybe defaultStandardColours $ mkList8 [ createColour 0 43 54 -- base03 , createColour 203 75 22 -- orange , createColour 88 110 117 -- base01 , createColour 101 123 131 -- base00 , createColour 131 148 150 -- base0 , createColour 108 113 196 -- violet , createColour 147 161 161 -- base1 , createColour 253 246 227 -- base3 ] -- This is our Solarized light 'ColourConfig'. It holds all of our light-related settings. solarizedLight :: ColourConfig (AlphaColour Double) solarizedLight = defaultColourConfig -- Set the default foreground colour of text of the terminal. { foregroundColour = Set (createColour 101 123 131) -- base00 , backgroundColour = Set (createColour 253 246 227) -- base3 -- Set the extended palette that has 2 Vecs of 8 Solarized palette colours , palette = ExtendedPalette solarizedLight1 solarizedLight2 } where solarizedLight1 :: List8 (AlphaColour Double) solarizedLight1 = fromMaybe defaultLightColours $ mkList8 [ createColour 7 54 66 -- base02 , createColour 220 50 47 -- red , createColour 133 153 0 -- green , createColour 181 137 0 -- yellow , createColour 38 139 210 -- blue , createColour 211 54 130 -- magenta , createColour 42 161 152 -- cyan , createColour 238 232 213 -- base2 ] solarizedLight2 :: List8 (AlphaColour Double) solarizedLight2 = fromMaybe defaultLightColours $ mkList8 [ createColour 0 43 54 -- base03 , createColour 203 75 22 -- orange , createColour 88 110 117 -- base01 , createColour 101 123 131 -- base00 , createColour 131 148 150 -- base0 , createColour 108 113 196 -- violet , createColour 147 161 161 -- base1 , createColour 253 246 227 -- base3 ] -- This defines the font for the terminal. fontConf :: FontConfig fontConf = defaultFontConfig { fontFamily = "Monospace" , fontSize = FontSizePoints 12 } main :: IO () main = do -- First, create the colour extension based on either Solarized modules. myColourExt <- createColourExtension solarizedDark -- Update 'myTMConfig' with our colour extension. let newTMConfig = addColourExtension myTMConfig myColourExt -- Start Termonad with our updated 'TMConfig'. start newTMConfig termonad-4.5.0.0/flake.lock0000644000000000000000000000106707346545000013620 0ustar0000000000000000{ "nodes": { "nixpkgs": { "locked": { "lastModified": 1680213900, "narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=", "owner": "NixOS", "repo": "nixpkgs", "rev": "e3652e0735fbec227f342712f180f4f21f0594f2", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "nixpkgs": "nixpkgs" } } }, "root": "root", "version": 7 } termonad-4.5.0.0/flake.nix0000644000000000000000000000420407346545000013462 0ustar0000000000000000{ description = "A VTE-based terminal emulator configurable in Haskell"; # Nixpkgs / NixOS version to use. inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; outputs = { self, nixpkgs }: let # Generate a user-friendly version numer. version = builtins.substring 0 8 self.lastModifiedDate; # System types to support. # # TODO: Since callCabal2nix uses IFD, adding multiple systems to # supportedSystems doesn't currently work with flakes. Commands like # `nix flake check` and `nix flake show` don't work. # # https://github.com/NixOS/nix/issues/4265 # # Termonad is likely to also work aarch64-linux, but you'll have to edit # this line to enable it. supportedSystems = [ "x86_64-linux" ]; # Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'. forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system); # Nixpkgs instantiated for supported system types. nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; overlays = [ self.overlay ]; }); in { # A Nixpkgs overlay that defines Termonad. overlay = final: prev: let overlays = import ./.nix-helpers/overlays.nix; in prev.lib.composeManyExtensions overlays final prev; # Provide some binary packages for selected system types. packages = forAllSystems (system: let pkgs = nixpkgsFor.${system}; in { termonad = pkgs.termonad-with-packages; } ); # The default package for 'nix build'. This makes sense if the # flake provides only one package or there is a clear "main" # package. defaultPackage = forAllSystems (system: self.packages.${system}.termonad); devShell = forAllSystems (system: nixpkgsFor.${system}.termonadShell); defaultApp = forAllSystems (system: self.apps.${system}.termonad); apps = forAllSystems (system: { termonad = { type = "app"; program = "${self.packages.${system}.termonad}/bin/termonad"; }; }); }; } termonad-4.5.0.0/glade/0000755000000000000000000000000007346545000012734 5ustar0000000000000000termonad-4.5.0.0/glade/README.md0000644000000000000000000000120707346545000014213 0ustar0000000000000000 # Termonad Glade Files This directory contains [Glade](https://wiki.gnome.org/Apps/Glade) files used by Termonad to define how the GTK interfaces should look. Glade is an visual interface designer. The Glade files are just XML files. They can be edited in the Glade program, or directly by hand. If you're new to Glade, you may be interested in going through a [tutorial](https://wiki.gnome.org/Apps/Glade/Tutorials). If you use the [shell.nix](../shell.nix) file, it should pull in `glade` for you, so you can just run it directly after entering into the Nix shell. Or, you can just install `glade` through your package-manager-of-choice. termonad-4.5.0.0/glade/preferences.glade0000644000000000000000000003626007346545000016242 0ustar0000000000000000 False Termonad Preferences center-on-parent dialog False 10 10 8 8 8 8 True vertical 10 False end gtk-ok True True True True True True 1 False False 0 True False True 0 7 10 True True True Font to use in the terminal. True False Sans 12 en-us 1 0 True False Font: 0 0 True True Number of lines to keep in the output of the terminal. 1 2 True False Scrollback length: 0 2 Confirm exit True True False Whether or not to pop-up a dialog asking if you are sure you want to exit when you close a tab or exit out of Termonad. start True 1 7 True False Word char exceptions: 0 3 True True When double-clicking on text in the terminal with the mouse, Termonad will use these characters to determine what to highlight. Characters in this list will be counted as part of a word. This makes it easy to highlight things like URLs or file paths. 1 3 Show menu True True False Whether or not to show the menubar. (This is the bar at the top that has the "File", "Edit", "View", etc buttons.) start True 1 8 True False Show scrollbar: 0 1 True False Whether or not to show a scrollbar on the terminal. "If Needed" only shows the scrollbar if the text on the terminal goes off the screen. 1 1 True False Whether or not to show the tab bar. "If Needed" only shows the tab bar when you have multiple tabs. 1 4 True False Set whether the cursor in the terminal should blink. "System" sets this to the system-level GTK setting. 1 5 True False 5 5 5 5 Warning: these settings will be used for current session only. To make them permanent, set them in ~/.config/termonad/termonad.hs fill True 30 0 9 2 True False Show tabbar: 0 4 True False Cursor blink mode: 0 5 Bold is bright True True False Control whether or not to force bold text to use colors from the light palette. If selected, then colored bold text will always use colors from the light palette. There will be no way to print bold text colored with the standard palette. If unselected, then bold can be applied separately to colors from both the standard and light palettes. start True 1 6 Enable sixel True True False Enable sixel to draw graphics start True 1 10 Allow bold True True True Allow terminal to use bold text. start True 1 11 True True 3 1 ok termonad-4.5.0.0/img/0000755000000000000000000000000007346545000012434 5ustar0000000000000000termonad-4.5.0.0/img/termonad-lambda.png0000644000000000000000000002741707346545000016204 0ustar0000000000000000PNG  IHDR\rfzTXtRaw profile type exifxڭiv8cw `<-~.Srے(H{dML斳_lΛjϫ^ߎϷkxuUxqA)yk4޿Ϋg^e9_??XpJ;`5KPCݿ !}g~ c^lj9U]_}9T6E},ā+ý,SM/SY$bh~qy}\tuwܾML~«Ӈ{⛟7(Q?B`!F>L8?mqwv目2r)ρQ:g맯+1C_" |7K`L͕v;!Fr?r+8K6+5.b1.]H.;[/J|: .%9&Lp>0B RȡJ`Řȟ+9SHѤr*z9s.YK(K)kkڛoK-bZmΤ;Ww}FiQFmI8̳:˯(W1vTqwuv‰'|ʩWTߣ~_Gͽ{^51$ŌxQHhbbf('j.)8)bD0nqI_.rF9н"kܾ귣 U|j8akWOϯA6拡)ú^ng>Wڢ>NcE.[grN\d,5{,s0~wwטrwטrwט?]5Oq%}\ctIט?]57ymI$ȼ̷uO75)0:,}̹:>-i-|2=qk\QtiMP}xMe DU,v|vۓdx&k4&-`E|ĩD828ޞgS<Ԕ8*\ٴ]MZU$ɰ_k?A#O4vYXbNqꗰ[  e33n?b,DX SDnqi۩]$!bV~ʌR!ߝ7kFG6~>9r6]cl٧"!a_~' 8ff AΕ7gP폇6nZ cTNrmBch$↓my2Iؠ{6 %d>U=/B Wx,gRS'}@تx1;&|S_3uVXzSi0zC&,}3Rj+y4Y5Kfw:4XNlVZNԨMA%61 :~XCN/g*Gݥh$a//Tqsna=Pm2z'ĶZ\!Ro&\qzzM(e6/@17Q&F7Et,8_ / L},/sZ<^ 9De<of}"} T3KU^T/p%ShUHާ+C7&5z( xri{%*߃Ǐ?`LmV` PD8PzFIb!o,To8Dbyoc]I:d>;56x88KPR_bFfqWh[!9)ED(RҨ;r;u<.v?V9wi6囥ϐ}^n? @Jnaqt|S4\l9ZyXA{CU8"2q+xyrO' WL A`TM *.Coy_G9T*o&0RoDVepkE~fsy^kR>Bw&Ps3ljt~- $9jgA(*v,wYqN=4\J?_z:{ad|&i#:W=DnؐIK⺺ڻ+1r}Yvmڡ` &s! liFD(ĢJtk^p>2L }%fRKY~`z`ڼp䰝?{ * 95z n 4㾊ԩ(Ƃcax 95ؓvw_ȂvLqEԀ>xnoʳ )GЋƕSC`4˶O({S@^ f`IŽRK9-2Fߦ@VǟI:i]vja u~G~f|l J[^ePУ/g{s)xœ7W@"_&k$u՗/l%dKt\\XA)D@{$V3Q(A922ij:+Q`^mBHaR AvAaT!Ƿћ/|ìKjIo5AAX7G0.;5%b!1H]PA`>(߰Mm[\*9>N= SAc G}bW֭Ri!XZYDlzVpצcuVIpf˜T"IqNv 8I3ޭw\cEQ΁',@ TITB Ʌ(@2s.eGP>!GX%>:7]0 ݑJC9['mG HKܜaoA3 [*qj"؍l%у!u,<:8T1 y0Q$='0iR8I$NtQl$~8ATD: $+o&ol&y:7X"vE"3ϳnIvK BAUNa:d{HH#G:%y-:# |%&#` +&q6sI8#WS!8m`r"A]¶LtS֨H4_YJѲT.^*yD\!09 č#A/W3Xj݈;D RLu+b[8T ̔æ{(@fX*uDGˠ!G=K;O2vm`6 32NF):Gʇ]g/cf[Vا ߐ/iY%Dy5rjҜgkQR10۩$CI+1'(rp˴3y .K4"ʑjAzQ,.'mr~ak|lH2EK:Z7c>㕕͂z0wѦ+I"#-;@AU Ax*im6zB[M6| 3YZah-0:;IQ"9+b:7g؈l6`GH jckj3F;j*w5(4xL@% MXi1u-6SiE@dh,nQ%`"+;и9;1Sզ>@¼4f&7_gAtsHg4eO6 ňt9z<Gmf4G *!.0>s25*;;?N{?]fw{'> x}JWj3еA !O~7w!=pcZIs m4JO2crѰ*^t mUT%3ӧ?Pb~>0/]`scΩ.FFБnz@u[v (dĢ~E_eA]'=& cζj!"6Svd9/} s\z3M)6en5s) g =_~= 7p*ׁAд UzBw mW=ą'U@5\ws^ bأ6<A3I)wuABVnY=ꌰoO|}5R_*km8 5x0Ig$TC0葓8Je'mY7eh݄ |V6n>1lqzB{-=&݈Vbrբ|WíJY u[S6,!u]OzD=Ktc}!2{S!'5zGz }=2Bϊz^@4\|=:,L[r]'FqY} !ǵ8R=s '@=σwjwI5io:+FST>g޿4Brн3ujWQ_ryl1v jr d߽߽+~*WOxDY`PL9I$84q? @,y5/vǷbKGD pHYs.#.#x?vtIME  )ZjIDATxYtZJnlPƱ62^Y 1L , !` @2g3'\̜\d&'C 0F,򆅑myQi_{_j.ٖmI]ݪ}bR߿꫿""""""""""""""""""""""""""""""""""""""" B4 "NdY(EQͥt8`AXFw!X+fF$yȲ\? WF5~/ f7yHT?a`F7x@hDQD>?; qD'&?O1_; =Fxh#.Wɧd<&@4]0*J+Cq+A)~u~r9~L/GehzDQTl>ڮFV'11뫠7S Dӛ٬?&ק<]Ktu$!}}|1']L4龂`%=h4]Mt嬯[{Ũp Y?i?SK&_DˉrL/Z;`9``2xf|^<>C\T|C,QF6{0R/"lv|?ͤG&+} b/PţTS ȤI,g4bաNj aڵX,|;r9ͬX% edf?vr[,Vj 7 V_L꣱d;QQgLfWr $0>hZ3J< b@e[{ I(rgNcAg}6Ydl1Ƞ>o1Ȁd@l1Ƞ>| 2 6dYM>q'k3Nk4K69W:|( Y}ش:kFZM chFzM Kx޾d8 ¾գ ֨><?s4PE M> Ax1h=bAK5 rըp[{ U} R׈M>cclsYfTE}*g EвLN s]#Qy1b7-)y`Q/Xa2 Ԩ3J#l?j\vz0o.Keqp\5*>lP3ÓU8Ba_D3*:bǁN3& f̅]sTΟ͒/ _ЇoޒMFxtE -lIDg\ԧqxQܻ" 7mlnK\k Z:PZp͟>?I rɼ {8HΗC=S$ftۡ^k Y@=n v7~}_\MV4[Z>D/cvMAx/gSo0 vuTaOe hY(^#@0ّn`x! AE@`[=!5!P(:u"Q#AdBO-B jܳ_0ru|,DozS׌w&l2 ԅZz0/OcsMNڛj%ƩAV"j4)v `0.q~̂?u#ˉ/,WU:RhQ;A0FxXDʁ/10ބ=@E%¾$\0e$ൎWS^1ӕ|q3>8Q't"Oj nMos-j))R)ŻGpג.=B<xyn)%\2~p(^ #U\Ƽx56vAnOKcI 6v+HPsx1wEx+cEmAPʯ8e=1ܿbҋZOPtҢ :P-u<*Y^qxxy>GO{SJ*ožli䞨Qf5ֆA<֘D@K>n{3 r֏:wE xdy~] r#/Mv)EW"UTEc;Z.2* .;~@2^7Ĺ.% \̓>~d\_vʀ {mG`!e֯t~< PK:v[C^QNd0TԀUq`<3pjWK:z؃B B+,k4kn4G8UVrܮv:[~ g>Q^aGG`ӝCh̲FՂpfa ;fحYtj|+ר"Lf*|:_~_BMwb ը:Bx/Z8U^d|ߖVo핢?x* Mi 9d<&{#W'&ÇGxg }-'pRzl >hk-fpMk#y9}87zz)-5J > a> sM/hOSkGn\ pw4:ά_w}h(ǂxR\(gPnߥXJ+?vUN+MGfUX6Ww-zeThݻuŶ9ro6}Rh^a\9pto4p Ge6jA7*IŶ;Ouxe}I5ﯟǓkGjrM=q{FOeb1O5&q*s!|t4W^;jnY,U)|m Q V-|/|$/'V 㶨M=Y6c3[BYZЧhSoF~o9ySybZ9Mlގz9[,x`Mlކaߏ=xHn錢KBlfKPX5J=_stifUk;OWUS.ӝά[{v%k4XkK.%fC-8y1w-AK:W#3N^aۡ9eHO,}ɏ#}Z~uku0~ׇC=G:{xg:Yї2]|p/i,ӽ/w/?ƫ\KzhűxUߟFGχ@;:l aA7e]/mrao%$pJGrL%zjI !Zp.)e{wsֿ?o PrkOs&`n11y>*~Y0yGd`ȀYȠ&M>"g}"|D 6  l4#2`N9 &>0Z@Ph@E#2ZȲ\.}"#|D 6  l42 +Ad@}$쁨k@.!*%ǚbI'g@ɱX#e.&*%YJ HĆJ($ CgJ@gWt,K&IcKl4McCHlZmK񁳙${ #bs A6,ppyXܛDӽH^Q !Y ̽LT8͗%HD~@;kDKcS yO %1@/^Dؾebmn{Y7"GA~6\7b~}YC"͉ ?H$+ֱDs \o"ȣ@#kKZmMmaLD<`DTDsz`7 MH$R/^}mXJ/t3W~A@/Xj)B'+<vJ(9QSbh25e, X(n=KЊ,L,vJD w5<P{X֖`\\R: #Ԭ-t&jL Uz%(Yg+%3T{}\ldjCl%P)s.VVnpw35\;+Qv?a jp{^`x 0gZh>(˲՘qwRPv]=KE1J&Yfa}=~0ӒP jB{e9U' `WgW .AWsO{Y ?C b~/XP>R\Kפ*;&! `ƃ? ]>̗ `ڃ {0{p}%4g'la%t[ YIp^XPa)x 09u-<0Yc^@?+a3x@D "be_Kt>N;Y r:iٜg%.k /pK}0Peн/}W fD """""""""""""""""""""""""""""""""""""""""E?1BGEIENDB`termonad-4.5.0.0/img/termonad.png0000644000000000000000000003553207346545000014763 0ustar0000000000000000PNG  IHDRU!bKGD3'| pHYs  tIME/m IDATxw|e}f8;mރY-Cč(Sp?WEDDE"Q Zҕi4{GA[$hfs]:>1,=kVѣGp80 9YE(ꫫV^gƮ0SOY^z)P41MS&l66 O\yU0SOY_|1@@ trUWܹss}ݚ`0ꈈ.c_=z*""""2”ax^n""""#L(p8T tb,K,¦2\ """" """"0("""" """"222"Wq˫|ԧ;&|'9բ[wBpYEDaPDF xB`6dsӘsrRDDv;fL< x;:wx/~KaPDv >ݽpW g"oT~>7l5ap-g[YyC~\A9^xQ}@WjnQ0/&`RjaƒtЭbΙ7QI ON610Uߧ*7ۺmW<z~ VV6ʠIo sMw\%l_^7އ0rޥ\V,0+K`A>)+hV6=swL&˃{3.Q[ ח2;u6fXD@Lx"pUDHe~!s5="VIt8Kj1|_Sݴw׶z\yMA *guYwggcl_˚uYohE3etuuY*ȤAAQAQAQA>OaGUaӫ*0믨 """"#Q__g """"#M,"""0("""" """"0("""" """"0("""" """"2àns}_BYxWS[ tsQN u8̿kr*c>ŵp+}ь/ y!҅77K89:K?-,$"{}\TCEDD~/X^/v:7Q m|p~22waw \xg䢯<=g&r]spK?@53Xv]u|9 kOȁ xe0i!_dtWe2QZK[w؀".DuR6_3Y邼h#W8G_')_e}S/SI' f,3/笉}]}-ŷ:N(S"""20 z9!QSb돯-8a\:XuTFqg_Ӭodᬙ= cye{=& """1a88{03$I  sȚ 7vfLT=cU|hZ6Fwٴx5?m>FhUPDDD>0傶Vafp6< On'loڲ ? 8$'oZV>]D:;2>5 Ds )~O<2sMTdN8kx廹7~! [rL.8|A@o̰Ş]OlGӿnyx̖37gG3ąk"ӈԆ&5۷7vdQzAc/ql."""d#@("""eAQE `ZQР*0X[]Ua ##S:;;S!DdDlvALaPDDDDaPDDDDEDDDDaPDDDDEDDDDaPDDDDEDDDDaPDDDDEDDDDaPd,½]tLBDFޖrs|*1 eGY9A2a\1b7:jhgd111xR3J¾oØn?SIR4t'Uv,@ .L;YsGp$M9ˆ`6-~I8CN1ILHĊиi Yt[Ĥd˄'`JƞƐO2p8݄ٴx\{י u(T?xֱs˟Yx ?b )w;~^0hhYIie#A >Hf4q&Cl`O-lk6҈+)e)D[FAL =Ope,l ٠ nYRl-eڔ̜1C?[j6g"ϛ$*[ÓoOaQ2e.}&13w[N\~Ʃ.j(ݸg?XWpsIwf'*Fq=7qQ~dD&GsdIOnmoc[kX8#uO;}& -ڎ%.m+@㖍lnqY4qi^l wnc;9{gf'1ڽX6qN})Wqi'~F2o<>X:N=u b_ʝEqd$0s',ei!mzYX.c8;/jbsaPb*w^4)\yd zATbvʑà;5BIJOclL4:ƐM3-]x2PMUN;@vA!uVRl6i.SEWVTI8ɱZQAgX Z15FOC۶7ĥ0j\.'XNvURօ/ ƓIAA6]LGMնˌiDVQh<ȯo!eP{5Nٚ{8wm5&|_>s 1idQw"v)ZCȖ -K]Wמē/gk5;pvm m'L ;1N ZԕQ酨HNʋY6+(;f*J*h HNNLOk˞@йaݖH"+)a׭gkC/ r]+Y޽;KYWь'Έ" PD :1bU|-Vְ|Y1.r҈ {ilL""HcLv"|v >vl(fCu+~g)J7Py8bѳ6좱b mCiۏ];Y6o$F0륾":9ʷP`Y97k,RSRYcOWO'- ddd1uԣ?a {25[O^)4\լxW|/RN&/=b^|:c,nz)- `=Y#^x/e/'̂əD9#4 |'7]tNi"\^-7/ш!:%d:Yj:p1I5?{y?XT˜Rȟ)IIL=s\tD<Co1zX`5(=t-_AL}X${+_\ CӬ/emm+[7>j(-Bٱ[-2FgյU/K k69`;\76ȿŝxl^̲\76UeJs{;]V"q6;Md!^fO&8bIJ''ņk48l @9 eeS8YQ}Il$:CaQSʶfH-K&lqk ErNZnrw_38v4 fN$0Z.jk5&"2ߞbwC(\{~&3m|4D2Hv35?4K}s7c?CXk׬cΜYzZ235cto&'3/q`U2O;w~ 7YI/i_o+eigPȺRvEw&{t&G}ū5cCgϊz7WUS."d%kpjLAr3۸nAFߩzJCd4,[7Lf|oX)DG8 6?$.]zɧc i2xW7p޼[W^ťw}:H<NΣ}m6Prv^Ғm`<[?g6M46L.fk)335;ju{&H„$XaB!0\ݿ pt Adm0ڎA8Cq'8ć~´wbb$8PwК `|Σq&+W}@O<˖4y"kV}ou?j3ӿ~%Xg3ÔX_BIiee]=)<)|w`J>(^5kY*:o g:$s57Wd؇g&7=^z|`Ĺw^,7iu[NWR2 oۃ8? e|]hEx4\ĥdQP4iy1 ⊍]#cѿӅ۰aذzZo:jhyHqZgcb;t>*aBw|mt[m|xL&7|C3@Gc;[{^Vm!xxCoC{ٕA_#e5,uY`hݾwr.#ذN+4Ni 6Zh$Nܽ,mHU5>~z3QNbrio2ǺgV֭ A3i>bǓoQʥn}%˩A\J&X!h iξuRex2Sj;r6ÓCnL3*V$)2@[sa¬X{otbodSs{qٝXKpt#9D?*K  N&'7pf$c1{!dʾIDATy,[su>coo1Ӧo"1bM;8RyGolG--/Qe&SOWӈN֮_)3&[1OwPZ6F [ٺ.t.>7rmǎ'vxgx!v7G\FۼO4 ZNwӻJ`wK=ĸ3KR\A3VgxR<-3MOv{:N$wl<6FQ^ښv2pDƓ=nl@S⪪mK4 Zd"57ul.gFA3hbv0 pDD9JOaoխM4-2SqCY7&$leUԶ"ʓ؂TwLˬF4Sfǽn~XfMٳi̯\BỏqXV>.`P5Yc#*t:tθ{Ŀ77ֿ6E~QnA9c<6}~,߻ n{&5;ؒD@Sqυ2>ÿ6a"Yd%C#fq ˼y3k[bIx:W\9fذHRƑV@Lܩv&eqCiLȶ"c :6mt1m|a;Dt G\F<_222NJގ3c[jk:0b3FblQNvwHQߦoaܼd#o:;;;lV/I+;#x.8{r2H3i6o<ǧ!{nDۈi7׏Y]N#Yt5l$&yۉ˟WSgЕ4H4o8al1EǓ2FP;Ĺ.Ad{,N]g͘sn䎬kKyK$~K%M;!y/9+k m#G\_hhő kg&J Jh\ O߯Y,w 'p2ڿfsh!v"PMUlKxmlD^˨H n` 'cYJew _':6O+q[_"+POl)igg-sV28ϣp&__Άǂ} laaK$^pLJlM,O imk4 KH8hm\#_ϤI=oкG85䳣quf*KEL{...z "Oɠ)>@*dqwvѱ.aOH=vTB}cۙf;Ha7zt~7޾G mԋwi= 'оqL{!غC""Io%9vUAX2%ǯ ("2LàaV^ Xj)9n+H"@wO _H&'|½4=FxM9>mMl:c-r=;DrNei*O\,w4:@;>n+fS<MCll*tF? ɎZJ+a0v]! )^: 0F+u: b1SHSRHkk K mXV 1#F+U=9( 0I? iD;쳓Qˎ{bV6r$'v[*B;d9#7<B4ry`ꄯjOՄ{;V"yp{i{,,]ͬgQuz 0Ň>s? bmn~i.'ƑzYxlES tJ)7b>7s3!w[xK 8q=D01v`W0߳;_ھAGbݿ?N6>W&v=\{$/Lc9.$vە-7t^> [F;aH޾9t% S!8h4_d[i*;M)x #*b zbGͯԮ ֯^CFHKXg*ў7ۚ3;EDg<îD/i%dbYHN 4<ճgmY}_?t~)yjn$x. q^7gZXa7HWKbvTpyU͙vtx-Y kiL"'}8}ھAGkbwN55o>˅IK6z[|&]t i8p=O1aj .l9 ğ!ΗGmmt4MGf-_B}Y]WyQ'4;ʵ|K 4I h>rB}{pgqD;,y6VSsm9V_?hmmqߒ۵k%N#v8Vz|1gkf5y!8`j3{kiE I`d'H ,|&:V~يrF7ohJ\$;P/MZZ;;{*xoy `bQ}g؈8=i=>2شV쑺레01 vR^R´=ѴO^'cL{>=sG3v7=T!'z:X=mli+I&pqD@ Ty25pdMk_(Y21cq+ g+|tmwLey>!6䰶o0ڟ4T"20\8"w]E,3#Hw4}&h]S /H҅Ә~8"B|wpТjjIdI<6 cgp^=Ń=̿%5r1kA+~ >2ɄE])`mu+zC1>}ˤ^&BVf|*.AtUC;whWHNPh_H緰b{dk/6'^vA,wA¨ga5|zBZz{JZne]}"lQi'о?[ʺWӴ3ʉgظ`+̓ o l^ld19$xؾZADSN'9w%pDh8Ϳh:QA?i Ď#xEkR1X͗eY'+i%ovlNrZDd0^_H,.vV~݄q1c'?NzI5Z;*1x>GLwX }ʂ1N%} ŵ*0 ᄞ+WǏ$fz:۶cVs@KH9 q]V`"oֻEDAIĹ-۽켵훎.4h2X_hu"" wT9oKl* (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  ( (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  ( (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  ( (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  ( (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  ( (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  (  ( oj@ eg8U̴ |+/d&bIENDB`termonad-4.5.0.0/shell.nix0000644000000000000000000000226507346545000013514 0ustar0000000000000000# Running `nix-shell` in the same directory as this file will enter an # environment in which you can easily hack on or build termonad. It provides: # * Termonad's system dependencies. # * GHC with all of termonad's haskell dependencies. # * Local haddocks for those dependencies. # * Hoogle with an index generated from those haddocks. # * Cabal. # * ghcid. # * open-haddock. # # In this environment you should be able to e.g. # * Build termonad: # $ cabal new-build # * Open a repl with access to the termonad libraries: # $ cabal new-repl # * Use `ghcid`: # $ ghcid --command='cabal new-repl' # * Open local haddocks for e.g. GI.Vte.Objects.Terminal: # $ open-haddock GI.Vte.Objects.Terminal # or for the gi-gtk package: # $ open-haddock gi-gtk # # If you pass nix-shell the arguments `--arg indexTermonad true`, then hoogle # will also index the Termonad libraries, however this will mean the environment # will need to be rebuilt every time the termonad source changes. { nixpkgs ? null , additionalOverlays ? [] , compiler ? null , buildExamples ? false , enableSixelSupport ? false , indexTermonad ? false }@args: (import .nix-helpers/nixpkgs.nix args).termonadShell termonad-4.5.0.0/src/0000755000000000000000000000000007346545000012447 5ustar0000000000000000termonad-4.5.0.0/src/Termonad.hs0000644000000000000000000000075107346545000014557 0ustar0000000000000000-- | Module : Termonad -- Description : Termonad -- Copyright : (c) Dennis Gosnell, 2018 -- License : BSD3 -- Stability : experimental -- Portability : POSIX -- -- This module exposes termonad's basic configuration options, as well as 'defaultMain'. -- -- If you want to configure Termonad, please take a look at "Termonad.Config". module Termonad ( defaultMain , start , module Config ) where import Termonad.App (defaultMain, start) import Termonad.Config as Config termonad-4.5.0.0/src/Termonad/0000755000000000000000000000000007346545000014220 5ustar0000000000000000termonad-4.5.0.0/src/Termonad/App.hs0000644000000000000000000010661307346545000015303 0ustar0000000000000000module Termonad.App where import Termonad.Prelude import Config.Dyre (defaultParams, projectName, realMain, showError, wrapMain) import Control.Lens ((.~), (^.), (^..), over, set, view) import Control.Monad.Fail (fail) import Data.FocusList (focusList, moveFromToFL, updateFocusFL) import Data.Sequence (findIndexR) import GI.Gdk (castTo, managedForeignPtr, screenGetDefault) import GI.Gio ( ApplicationFlags(ApplicationFlagsFlagsNone) , MenuModel(MenuModel) , actionMapAddAction , applicationQuit , applicationRun , onApplicationActivate , onApplicationStartup , onSimpleActionActivate , simpleActionNew ) import GI.Gtk ( Application , ApplicationWindow(ApplicationWindow) , Box(Box) , CheckButton(CheckButton) , ComboBoxText(ComboBoxText) , Dialog(Dialog) , Entry(Entry) , FontButton(FontButton) , Label(Label) , PolicyType(PolicyTypeAutomatic) , PositionType(PositionTypeRight) , ResponseType(ResponseTypeAccept, ResponseTypeNo, ResponseTypeYes) , ScrolledWindow(ScrolledWindow) , SpinButton(SpinButton) , pattern STYLE_PROVIDER_PRIORITY_APPLICATION , aboutDialogNew , adjustmentNew , applicationAddWindow , applicationGetActiveWindow , applicationSetAccelsForAction , applicationSetMenubar , applicationWindowSetShowMenubar , boxPackStart , builderNewFromString , builderSetApplication , comboBoxGetActiveId , comboBoxSetActiveId , comboBoxTextAppend , containerAdd , cssProviderLoadFromData , cssProviderNew , dialogAddButton , dialogGetContentArea , dialogNew , dialogResponse , dialogRun , entryBufferGetText , entryBufferSetText , entryGetText , entryNew , fontChooserSetFontDesc , fontChooserGetFontDesc , getEntryBuffer , gridAttachNextTo , gridNew , labelNew , notebookGetNPages , notebookNew , notebookSetShowBorder , onEntryActivate , onNotebookPageRemoved , onNotebookPageReordered , onNotebookSwitchPage , onWidgetDeleteEvent , scrolledWindowSetPolicy , setWidgetMargin , spinButtonGetValueAsInt , spinButtonSetAdjustment , spinButtonSetValue , styleContextAddProviderForScreen , toggleButtonGetActive , toggleButtonSetActive , widgetDestroy , widgetGrabFocus , widgetSetCanFocus , widgetSetVisible , widgetShow , widgetShowAll , windowPresent , windowSetDefaultIconFromFile , windowSetTitle , windowSetTransientFor ) import qualified GI.Gtk as Gtk import GI.Pango ( FontDescription , pattern SCALE , fontDescriptionGetFamily , fontDescriptionGetSize , fontDescriptionGetSizeIsAbsolute , fontDescriptionNew , fontDescriptionSetFamily , fontDescriptionSetSize , fontDescriptionSetAbsoluteSize ) import GI.Vte ( CursorBlinkMode(..) , catchRegexError , regexNewForSearch , terminalCopyClipboard , terminalPasteClipboard , terminalSearchFindNext , terminalSearchFindPrevious , terminalSearchSetRegex , terminalSearchSetWrapAround , terminalSetBoldIsBright , terminalSetCursorBlinkMode , terminalSetFont , terminalSetScrollbackLines , terminalSetWordCharExceptions , terminalSetAllowBold ) import System.Environment (getExecutablePath) import System.FilePath (takeFileName) import Paths_termonad (getDataFileName) import Termonad.Gtk (appNew, objFromBuildUnsafe, terminalSetEnableSixelIfExists) import Termonad.Keys (handleKeyPress) import Termonad.Lenses ( lensBoldIsBright , lensEnableSixel , lensAllowBold , lensConfirmExit , lensCursorBlinkMode , lensFontConfig , lensOptions , lensShowMenu , lensShowScrollbar , lensShowTabBar , lensScrollbackLen , lensTMNotebook , lensTMNotebookTabTermContainer , lensTMNotebookTabs , lensTMNotebookTabTerm , lensTMStateApp , lensTMStateAppWin , lensTMStateConfig , lensTMStateFontDesc , lensTMStateNotebook , lensTerm , lensWordCharExceptions ) import Termonad.PreferencesFile (saveToPreferencesFile) import Termonad.Term ( createTerm , relabelTabs , termNextPage , termPrevPage , termExitFocused , setShowTabs , showScrollbarToPolicy ) import Termonad.Types ( ConfigOptions(..) , FontConfig(..) , FontSize(FontSizePoints, FontSizeUnits) , ShowScrollbar(..) , ShowTabBar(..) , TMConfig , TMNotebookTab , TMState , TMState'(TMState) , getFocusedTermFromState , modFontSize , newEmptyTMState , tmNotebookTabTermContainer , tmNotebookTabs , tmStateApp , tmStateNotebook ) import Termonad.XML (interfaceText, menuText, preferencesText) setupScreenStyle :: IO () setupScreenStyle = do maybeScreen <- screenGetDefault case maybeScreen of Nothing -> pure () Just screen -> do cssProvider <- cssProviderNew let (textLines :: [Text]) = [ "scrollbar {" -- , " -GtkRange-slider-width: 200px;" -- , " -GtkRange-stepper-size: 200px;" -- , " border-width: 200px;" , " background-color: #aaaaaa;" -- , " color: #ff0000;" -- , " min-width: 4px;" , "}" -- , "scrollbar trough {" -- , " -GtkRange-slider-width: 200px;" -- , " -GtkRange-stepper-size: 200px;" -- , " border-width: 200px;" -- , " background-color: #00ff00;" -- , " color: #00ff00;" -- , " min-width: 50px;" -- , "}" -- , "scrollbar slider {" -- , " -GtkRange-slider-width: 200px;" -- , " -GtkRange-stepper-size: 200px;" -- , " border-width: 200px;" -- , " background-color: #0000ff;" -- , " color: #0000ff;" -- , " min-width: 50px;" -- , "}" , "tab {" , " background-color: transparent;" , "}" ] let styleData = encodeUtf8 (unlines textLines :: Text) cssProviderLoadFromData cssProvider styleData styleContextAddProviderForScreen screen cssProvider (fromIntegral STYLE_PROVIDER_PRIORITY_APPLICATION) createFontDescFromConfig :: TMConfig -> IO FontDescription createFontDescFromConfig tmConfig = do let fontConf = tmConfig ^. lensOptions . lensFontConfig createFontDesc (fontSize fontConf) (fontFamily fontConf) createFontDesc :: FontSize -> Text -> IO FontDescription createFontDesc fontSz fontFam = do fontDesc <- fontDescriptionNew fontDescriptionSetFamily fontDesc fontFam setFontDescSize fontDesc fontSz pure fontDesc setFontDescSize :: FontDescription -> FontSize -> IO () setFontDescSize fontDesc (FontSizePoints points) = fontDescriptionSetSize fontDesc $ fromIntegral (points * fromIntegral SCALE) setFontDescSize fontDesc (FontSizeUnits units) = fontDescriptionSetAbsoluteSize fontDesc $ units * fromIntegral SCALE adjustFontDescSize :: (FontSize -> FontSize) -> FontDescription -> IO () adjustFontDescSize f fontDesc = do currFontSz <- fontSizeFromFontDescription fontDesc let newFontSz = f currFontSz setFontDescSize fontDesc newFontSz modifyFontSizeForAllTerms :: (FontSize -> FontSize) -> TMState -> IO () modifyFontSizeForAllTerms modFontSizeFunc mvarTMState = do tmState <- readMVar mvarTMState let fontDesc = tmState ^. lensTMStateFontDesc adjustFontDescSize modFontSizeFunc fontDesc let terms = tmState ^.. lensTMStateNotebook . lensTMNotebookTabs . traverse . lensTMNotebookTabTerm . lensTerm foldMap (\vteTerm -> terminalSetFont vteTerm (Just fontDesc)) terms fontSizeFromFontDescription :: FontDescription -> IO FontSize fontSizeFromFontDescription fontDesc = do currSize <- fontDescriptionGetSize fontDesc currAbsolute <- fontDescriptionGetSizeIsAbsolute fontDesc return $ if currAbsolute then FontSizeUnits $ fromIntegral currSize / fromIntegral SCALE else let fontRatio :: Double = fromIntegral currSize / fromIntegral SCALE in FontSizePoints $ round fontRatio fontConfigFromFontDescription :: FontDescription -> IO (Maybe FontConfig) fontConfigFromFontDescription fontDescription = do fontSize <- fontSizeFromFontDescription fontDescription maybeFontFamily <- fontDescriptionGetFamily fontDescription return $ (`FontConfig` fontSize) <$> maybeFontFamily compareScrolledWinAndTab :: ScrolledWindow -> TMNotebookTab -> Bool compareScrolledWinAndTab scrollWin flTab = let ScrolledWindow managedPtrFLTab = tmNotebookTabTermContainer flTab foreignPtrFLTab = managedForeignPtr managedPtrFLTab ScrolledWindow managedPtrScrollWin = scrollWin foreignPtrScrollWin = managedForeignPtr managedPtrScrollWin in foreignPtrFLTab == foreignPtrScrollWin updateFLTabPos :: TMState -> Int -> Int -> IO () updateFLTabPos mvarTMState oldPos newPos = modifyMVar_ mvarTMState $ \tmState -> do let tabs = tmState ^. lensTMStateNotebook . lensTMNotebookTabs maybeNewTabs = moveFromToFL oldPos newPos tabs case maybeNewTabs of Nothing -> do putStrLn $ "in updateFLTabPos, Strange error: couldn't move tabs.\n" <> "old pos: " <> tshow oldPos <> "\n" <> "new pos: " <> tshow newPos <> "\n" <> "tabs: " <> tshow tabs <> "\n" <> "maybeNewTabs: " <> tshow maybeNewTabs <> "\n" <> "tmState: " <> tshow tmState pure tmState Just newTabs -> pure $ tmState & lensTMStateNotebook . lensTMNotebookTabs .~ newTabs -- | Try to figure out whether Termonad should exit. This also used to figure -- out if Termonad should close a given terminal. -- -- This reads the 'confirmExit' setting from 'ConfigOptions' to check whether -- the user wants to be notified when either Termonad or a given terminal is -- about to be closed. -- -- If 'confirmExit' is 'True', then a dialog is presented to the user asking -- them if they really want to exit or close the terminal. Their response is -- sent back as a 'ResponseType'. -- -- If 'confirmExit' is 'False', then this function always returns -- 'ResponseTypeYes'. {- HLINT ignore "Reduce duplication" -} askShouldExit :: TMState -> IO ResponseType askShouldExit mvarTMState = do tmState <- readMVar mvarTMState let confirm = tmState ^. lensTMStateConfig . lensOptions . lensConfirmExit if confirm then confirmationDialogForExit tmState else pure ResponseTypeYes where -- Show the user a dialog telling them there are still terminals running and -- asking if they really want to exit. -- -- Return the user's resposne as a 'ResponseType'. confirmationDialogForExit :: TMState' -> IO ResponseType confirmationDialogForExit tmState = do let app = tmState ^. lensTMStateApp win <- applicationGetActiveWindow app dialog <- dialogNew box <- dialogGetContentArea dialog label <- labelNew $ Just "There are still terminals running. Are you sure you want to exit?" containerAdd box label widgetShow label setWidgetMargin label 10 void $ dialogAddButton dialog "No, do NOT exit" (fromIntegral (fromEnum ResponseTypeNo)) void $ dialogAddButton dialog "Yes, exit" (fromIntegral (fromEnum ResponseTypeYes)) windowSetTransientFor dialog win res <- dialogRun dialog widgetDestroy dialog pure $ toEnum (fromIntegral res) -- | Force Termonad to exit without asking the user whether or not to do so. forceQuit :: TMState -> IO () forceQuit mvarTMState = do tmState <- readMVar mvarTMState let app = tmState ^. lensTMStateApp applicationQuit app setupTermonad :: TMConfig -> Application -> ApplicationWindow -> Gtk.Builder -> IO () setupTermonad tmConfig app win builder = do termonadIconPath <- getDataFileName "img/termonad-lambda.png" windowSetDefaultIconFromFile termonadIconPath setupScreenStyle box <- objFromBuildUnsafe builder "content_box" Box fontDesc <- createFontDescFromConfig tmConfig note <- notebookNew widgetSetCanFocus note False -- If this is not set to False, then there will be a one pixel white border -- shown around the notebook. notebookSetShowBorder note False boxPackStart box note True True 0 mvarTMState <- newEmptyTMState tmConfig app win note fontDesc terminal <- createTerm handleKeyPress mvarTMState void $ onNotebookPageRemoved note $ \_ _ -> do pages <- notebookGetNPages note if pages == 0 then forceQuit mvarTMState else setShowTabs tmConfig note void $ onNotebookSwitchPage note $ \_ pageNum -> do modifyMVar_ mvarTMState $ \tmState -> do let notebook = tmStateNotebook tmState tabs = tmNotebookTabs notebook maybeNewTabs = updateFocusFL (fromIntegral pageNum) tabs case maybeNewTabs of Nothing -> pure tmState Just (tab, newTabs) -> do widgetGrabFocus $ tab ^. lensTMNotebookTabTerm . lensTerm pure $ tmState & lensTMStateNotebook . lensTMNotebookTabs .~ newTabs void $ onNotebookPageReordered note $ \childWidg pageNum -> do maybeScrollWin <- castTo ScrolledWindow childWidg case maybeScrollWin of Nothing -> fail $ "In setupTermonad, in callback for onNotebookPageReordered, " <> "child widget is not a ScrolledWindow.\n" <> "Don't know how to continue.\n" Just scrollWin -> do TMState{tmStateNotebook} <- readMVar mvarTMState let fl = tmStateNotebook ^. lensTMNotebookTabs let maybeOldPosition = findIndexR (compareScrolledWinAndTab scrollWin) (focusList fl) case maybeOldPosition of Nothing -> fail $ "In setupTermonad, in callback for onNotebookPageReordered, " <> "the ScrolledWindow is not already in the FocusList.\n" <> "Don't know how to continue.\n" Just oldPos -> do updateFLTabPos mvarTMState oldPos (fromIntegral pageNum) relabelTabs mvarTMState newTabAction <- simpleActionNew "newtab" Nothing void $ onSimpleActionActivate newTabAction $ \_ -> void $ createTerm handleKeyPress mvarTMState actionMapAddAction app newTabAction applicationSetAccelsForAction app "app.newtab" ["T"] nextPageAction <- simpleActionNew "nextpage" Nothing void $ onSimpleActionActivate nextPageAction $ \_ -> termNextPage mvarTMState actionMapAddAction app nextPageAction applicationSetAccelsForAction app "app.nextpage" ["Page_Down"] prevPageAction <- simpleActionNew "prevpage" Nothing void $ onSimpleActionActivate prevPageAction $ \_ -> termPrevPage mvarTMState actionMapAddAction app prevPageAction applicationSetAccelsForAction app "app.prevpage" ["Page_Up"] closeTabAction <- simpleActionNew "closetab" Nothing void $ onSimpleActionActivate closeTabAction $ \_ -> termExitFocused mvarTMState actionMapAddAction app closeTabAction applicationSetAccelsForAction app "app.closetab" ["W"] quitAction <- simpleActionNew "quit" Nothing void $ onSimpleActionActivate quitAction $ \_ -> do shouldExit <- askShouldExit mvarTMState when (shouldExit == ResponseTypeYes) $ forceQuit mvarTMState actionMapAddAction app quitAction applicationSetAccelsForAction app "app.quit" ["Q"] copyAction <- simpleActionNew "copy" Nothing void $ onSimpleActionActivate copyAction $ \_ -> do maybeTerm <- getFocusedTermFromState mvarTMState maybe (pure ()) terminalCopyClipboard maybeTerm actionMapAddAction app copyAction applicationSetAccelsForAction app "app.copy" ["C"] pasteAction <- simpleActionNew "paste" Nothing void $ onSimpleActionActivate pasteAction $ \_ -> do maybeTerm <- getFocusedTermFromState mvarTMState maybe (pure ()) terminalPasteClipboard maybeTerm actionMapAddAction app pasteAction applicationSetAccelsForAction app "app.paste" ["V"] preferencesAction <- simpleActionNew "preferences" Nothing void $ onSimpleActionActivate preferencesAction (const $ showPreferencesDialog mvarTMState) actionMapAddAction app preferencesAction enlargeFontAction <- simpleActionNew "enlargefont" Nothing void $ onSimpleActionActivate enlargeFontAction $ \_ -> modifyFontSizeForAllTerms (modFontSize 1) mvarTMState actionMapAddAction app enlargeFontAction applicationSetAccelsForAction app "app.enlargefont" ["plus"] reduceFontAction <- simpleActionNew "reducefont" Nothing void $ onSimpleActionActivate reduceFontAction $ \_ -> modifyFontSizeForAllTerms (modFontSize (-1)) mvarTMState actionMapAddAction app reduceFontAction applicationSetAccelsForAction app "app.reducefont" ["minus"] findAction <- simpleActionNew "find" Nothing void $ onSimpleActionActivate findAction $ \_ -> doFind mvarTMState actionMapAddAction app findAction applicationSetAccelsForAction app "app.find" ["F"] findAboveAction <- simpleActionNew "findabove" Nothing void $ onSimpleActionActivate findAboveAction $ \_ -> findAbove mvarTMState actionMapAddAction app findAboveAction applicationSetAccelsForAction app "app.findabove" ["P"] findBelowAction <- simpleActionNew "findbelow" Nothing void $ onSimpleActionActivate findBelowAction $ \_ -> findBelow mvarTMState actionMapAddAction app findBelowAction applicationSetAccelsForAction app "app.findbelow" ["I"] aboutAction <- simpleActionNew "about" Nothing void $ onSimpleActionActivate aboutAction $ \_ -> showAboutDialog app actionMapAddAction app aboutAction menuBuilder <- builderNewFromString menuText $ fromIntegral (length menuText) menuModel <- objFromBuildUnsafe menuBuilder "menubar" MenuModel applicationSetMenubar app (Just menuModel) let showMenu = tmConfig ^. lensOptions . lensShowMenu applicationWindowSetShowMenubar win showMenu windowSetTitle win "Termonad" -- This event will happen if the user requests that the top-level Termonad -- window be closed through their window manager. It will also happen -- normally when the user tries to close Termonad through normal methods, -- like clicking "Quit" or closing the last open terminal. -- -- If you return 'True' from this callback, then Termonad will not exit. -- If you return 'False' from this callback, then Termonad will continue to -- exit. void $ onWidgetDeleteEvent win $ \_ -> do shouldExit <- askShouldExit mvarTMState pure $ case shouldExit of ResponseTypeYes -> False _ -> True widgetShowAll win widgetGrabFocus $ terminal ^. lensTerm appActivate :: TMConfig -> Application -> IO () appActivate tmConfig app = do uiBuilder <- builderNewFromString interfaceText $ fromIntegral (length interfaceText) builderSetApplication uiBuilder app appWin <- objFromBuildUnsafe uiBuilder "appWin" ApplicationWindow applicationAddWindow app appWin setupTermonad tmConfig app appWin uiBuilder windowPresent appWin showAboutDialog :: Application -> IO () showAboutDialog app = do win <- applicationGetActiveWindow app aboutDialog <- aboutDialogNew windowSetTransientFor aboutDialog win void $ dialogRun aboutDialog widgetDestroy aboutDialog showFindDialog :: Application -> IO (Maybe Text) showFindDialog app = do win <- applicationGetActiveWindow app dialog <- dialogNew box <- dialogGetContentArea dialog grid <- gridNew searchForLabel <- labelNew (Just "Search for regex:") containerAdd grid searchForLabel widgetShow searchForLabel setWidgetMargin searchForLabel 10 searchEntry <- entryNew gridAttachNextTo grid searchEntry (Just searchForLabel) PositionTypeRight 1 1 widgetShow searchEntry setWidgetMargin searchEntry 10 -- setWidgetMarginBottom searchEntry 20 void $ onEntryActivate searchEntry $ dialogResponse dialog (fromIntegral (fromEnum ResponseTypeYes)) void $ dialogAddButton dialog "Close" (fromIntegral (fromEnum ResponseTypeNo)) void $ dialogAddButton dialog "Find" (fromIntegral (fromEnum ResponseTypeYes)) containerAdd box grid widgetShow grid windowSetTransientFor dialog win res <- dialogRun dialog searchString <- entryGetText searchEntry let maybeSearchString = case toEnum (fromIntegral res) of ResponseTypeYes -> Just searchString _ -> Nothing widgetDestroy dialog pure maybeSearchString doFind :: TMState -> IO () doFind mvarTMState = do tmState <- readMVar mvarTMState let app = tmStateApp tmState maybeSearchString <- showFindDialog app -- putStrLn $ "trying to find: " <> tshow maybeSearchString maybeTerminal <- getFocusedTermFromState mvarTMState case (maybeSearchString, maybeTerminal) of (Just searchString, Just terminal) -> do -- TODO: Figure out how to import the correct pcre flags. -- -- If you don't pass the pcre2Multiline flag, VTE gives -- the following warning: -- -- (termonad-linux-x86_64:18792): Vte-WARNING **: -- 21:56:31.193: (vtegtk.cc:2269):void -- vte_terminal_search_set_regex(VteTerminal*, -- VteRegex*, guint32): runtime check failed: -- (regex == nullptr || -- _vte_regex_get_compile_flags(regex) & PCRE2_MULTILINE) -- -- However, if you do add the pcre2Multiline flag, -- the terminalSearchSetRegex appears to just completely -- not work. let pcreFlags = 0 let newRegex = regexNewForSearch searchString (fromIntegral $ length searchString) pcreFlags eitherRegex <- catchRegexError (fmap Right newRegex) (\_ errMsg -> pure (Left errMsg)) case eitherRegex of Left errMsg -> do let msg = "error when creating regex: " <> errMsg hPutStrLn stderr msg Right regex -> do terminalSearchSetRegex terminal (Just regex) pcreFlags terminalSearchSetWrapAround terminal True _matchFound <- terminalSearchFindPrevious terminal -- TODO: Setup an actual logging framework to show these -- kinds of log messages. Also make a similar change in -- findAbove and findBelow. -- putStrLn $ "was match found: " <> tshow matchFound pure () _ -> pure () findAbove :: TMState -> IO () findAbove mvarTMState = do maybeTerminal <- getFocusedTermFromState mvarTMState case maybeTerminal of Nothing -> pure () Just terminal -> do _matchFound <- terminalSearchFindPrevious terminal -- putStrLn $ "was match found: " <> tshow matchFound pure () findBelow :: TMState -> IO () findBelow mvarTMState = do maybeTerminal <- getFocusedTermFromState mvarTMState case maybeTerminal of Nothing -> pure () Just terminal -> do _matchFound <- terminalSearchFindNext terminal -- putStrLn $ "was match found: " <> tshow matchFound pure () setShowMenuBar :: Application -> Bool -> IO () setShowMenuBar app visible = do void $ runMaybeT $ do win <- MaybeT $ applicationGetActiveWindow app appWin <- MaybeT $ castTo ApplicationWindow win lift $ applicationWindowSetShowMenubar appWin visible -- | Fill a combo box with ids and labels -- -- The ids are stored in the combobox as 'Text', so their type should be an -- instance of the 'Show' type class. comboBoxFill :: forall a. Show a => ComboBoxText -> [(a, Text)] -> IO () comboBoxFill comboBox = mapM_ go where go :: (a, Text) -> IO () go (value, textId) = comboBoxTextAppend comboBox (Just $ tshow value) textId -- | Set the current active item in a combobox given an input id. comboBoxSetActive :: Show a => ComboBoxText -> a -> IO () comboBoxSetActive cb item = void $ comboBoxSetActiveId cb (Just $ tshow item) -- | Get the current active item in a combobox -- -- The list of values to be searched in the combobox must be given as a -- parameter. These values are converted to Text then compared to the current -- id. comboBoxGetActive :: forall a. (Show a, Enum a) => ComboBoxText -> [a] -> IO (Maybe a) comboBoxGetActive cb values = findEnumFromMaybeId <$> comboBoxGetActiveId cb where findEnumFromMaybeId :: Maybe Text -> Maybe a findEnumFromMaybeId maybeId = maybeId >>= findEnumFromId findEnumFromId :: Text -> Maybe a findEnumFromId label = find (\x -> tshow x == label) values applyNewPreferences :: TMState -> IO () applyNewPreferences mvarTMState = do tmState <- readMVar mvarTMState let appWin = tmState ^. lensTMStateAppWin config = tmState ^. lensTMStateConfig notebook = tmState ^. lensTMStateNotebook . lensTMNotebook tabFocusList = tmState ^. lensTMStateNotebook . lensTMNotebookTabs showMenu = config ^. lensOptions . lensShowMenu applicationWindowSetShowMenubar appWin showMenu setShowTabs config notebook -- Sets the remaining preferences to each tab foldMap (applyNewPreferencesToTab mvarTMState) tabFocusList applyNewPreferencesToTab :: TMState -> TMNotebookTab -> IO () applyNewPreferencesToTab mvarTMState tab = do tmState <- readMVar mvarTMState let fontDesc = tmState ^. lensTMStateFontDesc term = tab ^. lensTMNotebookTabTerm . lensTerm scrolledWin = tab ^. lensTMNotebookTabTermContainer options = tmState ^. lensTMStateConfig . lensOptions terminalSetFont term (Just fontDesc) terminalSetCursorBlinkMode term (cursorBlinkMode options) terminalSetWordCharExceptions term (wordCharExceptions options) terminalSetScrollbackLines term (fromIntegral (scrollbackLen options)) terminalSetBoldIsBright term (boldIsBright options) terminalSetEnableSixelIfExists term (enableSixel options) terminalSetAllowBold term (allowBold options) let vScrollbarPolicy = showScrollbarToPolicy (options ^. lensShowScrollbar) scrolledWindowSetPolicy scrolledWin PolicyTypeAutomatic vScrollbarPolicy -- | Show the preferences dialog. -- -- When the user clicks on the Ok button, it copies the new settings to TMState. -- Then apply them to the current terminals. showPreferencesDialog :: TMState -> IO () showPreferencesDialog mvarTMState = do -- Get app out of mvar tmState <- readMVar mvarTMState let app = tmState ^. lensTMStateApp -- Create the preference dialog and get some widgets preferencesBuilder <- builderNewFromString preferencesText $ fromIntegral (length preferencesText) preferencesDialog <- objFromBuildUnsafe preferencesBuilder "preferences" Dialog confirmExitCheckButton <- objFromBuildUnsafe preferencesBuilder "confirmExit" CheckButton showMenuCheckButton <- objFromBuildUnsafe preferencesBuilder "showMenu" CheckButton boldIsBrightCheckButton <- objFromBuildUnsafe preferencesBuilder "boldIsBright" CheckButton enableSixelCheckButton <- objFromBuildUnsafe preferencesBuilder "enableSixel" CheckButton allowBoldCheckButton <- objFromBuildUnsafe preferencesBuilder "allowBold" CheckButton wordCharExceptionsEntryBuffer <- objFromBuildUnsafe preferencesBuilder "wordCharExceptions" Entry >>= getEntryBuffer fontButton <- objFromBuildUnsafe preferencesBuilder "font" FontButton showScrollbarComboBoxText <- objFromBuildUnsafe preferencesBuilder "showScrollbar" ComboBoxText comboBoxFill showScrollbarComboBoxText [ (ShowScrollbarNever, "Never") , (ShowScrollbarAlways, "Always") , (ShowScrollbarIfNeeded, "If needed") ] showTabBarComboBoxText <- objFromBuildUnsafe preferencesBuilder "showTabBar" ComboBoxText comboBoxFill showTabBarComboBoxText [ (ShowTabBarNever, "Never") , (ShowTabBarAlways, "Always") , (ShowTabBarIfNeeded, "If needed") ] cursorBlinkModeComboBoxText <- objFromBuildUnsafe preferencesBuilder "cursorBlinkMode" ComboBoxText comboBoxFill cursorBlinkModeComboBoxText [ (CursorBlinkModeSystem, "System") , (CursorBlinkModeOn, "On") , (CursorBlinkModeOff, "Off") ] scrollbackLenSpinButton <- objFromBuildUnsafe preferencesBuilder "scrollbackLen" SpinButton adjustmentNew 0 0 (fromIntegral (maxBound :: Int)) 1 10 0 >>= spinButtonSetAdjustment scrollbackLenSpinButton warningLabel <- objFromBuildUnsafe preferencesBuilder "warning" Label -- We show the warning label only if the user has launched termonad with a -- termonad.hs file executablePath <- getExecutablePath let hasTermonadHs = takeFileName executablePath == "termonad-linux-x86_64" widgetSetVisible warningLabel hasTermonadHs -- Make the dialog modal maybeWin <- applicationGetActiveWindow app windowSetTransientFor preferencesDialog maybeWin -- Init with current state fontChooserSetFontDesc fontButton (tmState ^. lensTMStateFontDesc) let options = tmState ^. lensTMStateConfig . lensOptions comboBoxSetActive showScrollbarComboBoxText $ showScrollbar options comboBoxSetActive showTabBarComboBoxText $ showTabBar options comboBoxSetActive cursorBlinkModeComboBoxText $ cursorBlinkMode options spinButtonSetValue scrollbackLenSpinButton (fromIntegral $ scrollbackLen options) toggleButtonSetActive confirmExitCheckButton $ confirmExit options toggleButtonSetActive showMenuCheckButton $ showMenu options toggleButtonSetActive boldIsBrightCheckButton $ boldIsBright options toggleButtonSetActive enableSixelCheckButton $ enableSixel options toggleButtonSetActive allowBoldCheckButton $ allowBold options entryBufferSetText wordCharExceptionsEntryBuffer (wordCharExceptions options) (-1) -- Run dialog then close res <- dialogRun preferencesDialog -- When closing the dialog get the new settings when (toEnum (fromIntegral res) == ResponseTypeAccept) $ do maybeFontDesc <- fontChooserGetFontDesc fontButton maybeFontConfig <- join <$> mapM fontConfigFromFontDescription maybeFontDesc maybeShowScrollbar <- comboBoxGetActive showScrollbarComboBoxText [ShowScrollbarNever ..] maybeShowTabBar <- comboBoxGetActive showTabBarComboBoxText [ShowTabBarNever ..] maybeCursorBlinkMode <- comboBoxGetActive cursorBlinkModeComboBoxText [CursorBlinkModeSystem ..] scrollbackLenVal <- fromIntegral <$> spinButtonGetValueAsInt scrollbackLenSpinButton confirmExitVal <- toggleButtonGetActive confirmExitCheckButton showMenuVal <- toggleButtonGetActive showMenuCheckButton boldIsBrightVal <- toggleButtonGetActive boldIsBrightCheckButton enableSixelVal <- toggleButtonGetActive enableSixelCheckButton allowBoldVal <- toggleButtonGetActive allowBoldCheckButton wordCharExceptionsVal <- entryBufferGetText wordCharExceptionsEntryBuffer -- Apply the changes to mvarTMState modifyMVar_ mvarTMState $ pure . over lensTMStateFontDesc (`fromMaybe` maybeFontDesc) . over (lensTMStateConfig . lensOptions) ( set lensConfirmExit confirmExitVal . set lensShowMenu showMenuVal . set lensBoldIsBright boldIsBrightVal . set lensEnableSixel enableSixelVal . set lensAllowBold allowBoldVal . set lensWordCharExceptions wordCharExceptionsVal . over lensFontConfig (`fromMaybe` maybeFontConfig) . set lensScrollbackLen scrollbackLenVal . over lensShowScrollbar (`fromMaybe` maybeShowScrollbar) . over lensShowTabBar (`fromMaybe` maybeShowTabBar) . over lensCursorBlinkMode (`fromMaybe` maybeCursorBlinkMode) ) -- Save the changes to the preferences files withMVar mvarTMState $ saveToPreferencesFile . view lensTMStateConfig -- Update the app with new settings applyNewPreferences mvarTMState widgetDestroy preferencesDialog appStartup :: Application -> IO () appStartup _app = pure () -- | Run Termonad with the given 'TMConfig'. -- -- Do not perform any of the recompilation operations that the 'defaultMain' -- function does. start :: TMConfig -> IO () start tmConfig = do -- app <- appNew (Just "haskell.termonad") [ApplicationFlagsFlagsNone] -- Make sure the application is not unique, so we can open multiple copies of it. app <- appNew Nothing [ApplicationFlagsFlagsNone] void $ onApplicationStartup app (appStartup app) void $ onApplicationActivate app (appActivate tmConfig app) void $ applicationRun app Nothing -- | Run Termonad with the given 'TMConfig'. -- -- This function will check if there is a @~\/.config\/termonad\/termonad.hs@ file -- and a @~\/.cache\/termonad\/termonad-linux-x86_64@ binary. Termonad will -- perform different actions based on whether or not these two files exist. -- -- Here are the four different possible actions based on the existence of these -- two files. -- -- - @~\/.config\/termonad\/termonad.hs@ exists, @~\/.cache\/termonad\/termonad-linux-x86_64@ exists -- -- The timestamps of these two files are checked. If the -- @~\/.config\/termonad\/termonad.hs@ file has been modified after the -- @~\/.cache\/termonad\/termonad-linux-x86_64@ binary, then Termonad will use -- GHC to recompile the @~\/.config\/termonad\/termonad.hs@ file, producing a -- new binary at @~\/.cache\/termonad\/termonad-linux-x86_64@. This new binary -- will be re-executed. The 'TMConfig' passed to this 'defaultMain' will be -- effectively thrown away. -- -- If GHC fails to recompile the @~\/.config\/termonad\/termonad.hs@ file, then -- Termonad will just execute 'start' with the 'TMConfig' passed in. -- -- If the @~\/.cache\/termonad\/termonad-linux-x86_64@ binary has been modified -- after the @~\/.config\/termonad\/termonad.hs@ file, then Termonad will -- re-exec the @~\/.cache\/termonad\/termonad-linux-x86_64@ binary. The -- 'TMConfig' passed to this 'defaultMain' will be effectively thrown away. -- -- - @~\/.config\/termonad\/termonad.hs@ exists, @~\/.cache\/termonad\/termonad-linux-x86_64@ does not exist -- -- Termonad will use GHC to recompile the @~\/.config\/termonad\/termonad.hs@ -- file, producing a new binary at @~\/.cache\/termonad\/termonad-linux-x86_64@. -- This new binary will be re-executed. The 'TMConfig' passed to this -- 'defaultMain' will be effectively thrown away. -- -- If GHC fails to recompile the @~\/.config\/termonad\/termonad.hs@ file, then -- Termonad will just execute 'start' with the 'TMConfig' passed in. -- -- - @~\/.config\/termonad\/termonad.hs@ does not exist, @~\/.cache\/termonad\/termonad-linux-x86_64@ exists -- -- Termonad will ignore the @~\/.cache\/termonad\/termonad-linux-x86_64@ binary -- and just run 'start' with the 'TMConfig' passed to this function. -- -- - @~\/.config\/termonad\/termonad.hs@ does not exist, @~\/.cache\/termonad\/termonad-linux-x86_64@ does not exist -- -- Termonad will run 'start' with the 'TMConfig' passed to this function. -- -- Other notes: -- -- 1. That the locations of @~\/.config\/termonad\/termonad.hs@ and -- @~\/.cache\/termonad\/termonad-linux-x86_64@ may differ depending on your -- system. -- -- 2. In your own @~\/.config\/termonad\/termonad.hs@ file, you can use either -- 'defaultMain' or 'start'. As long as you always execute the system-wide -- @termonad@ binary (instead of the binary produced as -- @~\/.cache\/termonad\/termonad-linux-x86_64@), the effect should be the same. defaultMain :: TMConfig -> IO () defaultMain tmConfig = do let params = defaultParams { projectName = "termonad" , showError = \(cfg, oldErrs) newErr -> (cfg, oldErrs <> "\n" <> newErr) , realMain = \(cfg, errs) -> putStrLn (pack errs) *> start cfg } eitherRes <- tryIOError $ wrapMain params (tmConfig, "") case eitherRes of Left ioErr | ioeGetErrorType ioErr == doesNotExistErrorType && ioeGetFileName ioErr == Just "ghc" -> do putStrLn $ "Could not find ghc on your PATH. Ignoring your termonad.hs " <> "configuration file and running termonad with default settings." start tmConfig | otherwise -> do putStrLn "IO error occurred when trying to run termonad:" print ioErr putStrLn "Don't know how to recover. Exiting." Right _ -> pure () termonad-4.5.0.0/src/Termonad/Config.hs0000644000000000000000000000341207346545000015761 0ustar0000000000000000-- | Module : Termonad.Config -- Description : Termonad Configuration Options -- Copyright : (c) Dennis Gosnell, 2018 -- License : BSD3 -- Stability : experimental -- Portability : POSIX -- -- This module exposes termonad's basic configuration options. To set these -- options in your config, first ensure you've imported "Termonad". -- -- Then for your main, apply 'Termonad.start' or 'Termonad.defaultMain' to a 'TMConfig' value. -- We suggest you build such values by performing record updates on the -- 'defaultTMConfig' rather than using the 'TMConfig' constructor, as the latter -- is much more likely to break when there are changes to the 'TMConfig' type. -- -- E.g. -- -- @ -- -- Re-exports this module. -- import "Termonad" -- -- main :: IO () -- main = 'start' $ 'defaultTMConfig' -- { 'showScrollbar' = 'ShowScrollbarNever' -- , 'confirmExit' = False -- , 'showMenu' = False -- , 'cursorBlinkMode' = 'CursorBlinkModeOff' -- } -- @ -- -- Additional options can be found in the following modules. -- -- * "Termonad.Config.Colour" -- -- If you want to see an example configuration file, as well as an explanation -- for how to use Termonad, see the Termonad -- . module Termonad.Config ( -- * Main Config Data Type TMConfig(..) , defaultTMConfig , ConfigOptions(..) , defaultConfigOptions , ConfigHooks(..) , defaultConfigHooks -- * Fonts , FontSize(..) , defaultFontSize , FontConfig(..) , defaultFontConfig -- * Misc , Option(..) , ShowScrollbar(..) , ShowTabBar(..) , CursorBlinkMode(..) , tmConfigFromPreferencesFile ) where import GI.Vte (CursorBlinkMode(..)) import Termonad.PreferencesFile (tmConfigFromPreferencesFile) import Termonad.Types termonad-4.5.0.0/src/Termonad/Config/0000755000000000000000000000000007346545000015425 5ustar0000000000000000termonad-4.5.0.0/src/Termonad/Config/Colour.hs0000644000000000000000000007747107346545000017244 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE TemplateHaskell #-} -- | Module : Termonad.Config.Colour -- Description : Termonad Configuration Colour Options -- Copyright : (c) Dennis Gosnell, 2018 -- License : BSD3 -- Stability : experimental -- Portability : POSIX -- -- To use this config extension in your @~\/.config\/termonad\/termonad.hs@, first -- import this module. Create a new 'ColourExtension' with the 'createColourExtension' function. -- Then add the 'ColourExtension' to your 'TMConfig' with the 'addColourExtension' function. -- -- See -- -- for a simple example. -- -- When setting colors, you may find it convenient to use the -- -- package, which provides an executable called @print-console-colors@ that prints -- all of the colors for your terminal. module Termonad.Config.Colour ( -- * Colour Config ColourConfig(..) , defaultColourConfig , List8 , List6 , List24 , Matrix , mkList8 , unsafeMkList8 , setAtList8 , overAtList8 , mkList6 , unsafeMkList6 , setAtList6 , overAtList6 , mkList24 , unsafeMkList24 , setAtList24 , overAtList24 , mkMatrix , unsafeMkMatrix , setAtMatrix , overAtMatrix -- ** Colour Config Lenses , lensCursorFgColour , lensCursorBgColour , lensForegroundColour , lensBackgroundColour , lensHighlightFgColour , lensHighlightBgColour , lensPalette -- * Colour Extension , ColourExtension(..) , createColourExtension , createDefColourExtension , addColourExtension , addColourConfig , colourHook , addColourHook -- * Palette , Palette(..) , defaultStandardColours , defaultLightColours , defaultColourCube , defaultGreyscale -- * Colour -- | Check out the "Data.Colour" module for more info about 'AlphaColour'. , AlphaColour , createColour , sRGB32 , sRGB32show , opaque , transparent -- * Debugging and Internal Methods , showColourVec , showColourCube , paletteToList , coloursFromBits , cube , setAt , overAt -- * Doctest setup -- $setup ) where import Termonad.Prelude hiding ((\\), index) import Control.Lens ((%~), makeLensesFor) import Data.Colour ( AlphaColour , Colour , affineCombo , alphaChannel , black , darken , opaque , over , transparent , withOpacity ) import Data.Colour.SRGB (RGB(RGB), toSRGB, toSRGB24, sRGB24) import qualified Data.Foldable import GI.Gdk ( RGBA , newZeroRGBA , setRGBAAlpha , setRGBABlue , setRGBAGreen , setRGBARed ) import GI.Vte ( Terminal , terminalSetColors , terminalSetColorCursor #ifdef VTE_VERSION_GEQ_0_44 , terminalSetColorCursorForeground #endif , terminalSetColorBackground , terminalSetColorForeground , terminalSetColorHighlight , terminalSetColorHighlightForeground ) import Text.Printf (printf) import Text.Show (showString) import Termonad.Lenses (lensCreateTermHook, lensHooks) import Termonad.Types ( Option(Unset) , TMConfig , TMState , whenSet ) -- $setup -- >>> import Data.Colour.Names (green, red) -- >>> import Data.Colour.SRGB (sRGB24show) ------------------- -- Colour Config -- ------------------- -- | This newtype is for length 8 lists. Construct it with 'mkList8' or 'unsafeMkList8' newtype List8 a = List8 { getList8 :: [a] } deriving (Show, Eq, Foldable, Functor) -- | Typesafe smart constructor for length 8 lists. mkList8 :: [a] -> Maybe (List8 a) mkList8 xs = if length xs == 8 then Just (List8 xs) else Nothing -- | Unsafe smart constructor for length 8 lists. unsafeMkList8 :: [a] -> List8 a unsafeMkList8 xs = case mkList8 xs of Just xs' -> xs' Nothing -> error $ "unsafeMkList8: input list contains " <> show (length xs) <> " elements. Must contain exactly 8 elements." -- | Set a given value in a list. -- -- >>> setAt 2 "hello" ["a","b","c","d"] -- ["a","b","hello","d"] -- -- You can set the first and last values in the list as well: -- -- >>> setAt 0 "hello" ["a","b","c","d"] -- ["hello","b","c","d"] -- >>> setAt 3 "hello" ["a","b","c","d"] -- ["a","b","c","hello"] -- -- If you try to set a value outside of the list, you'll get back the same -- list: -- -- >>> setAt (-10) "hello" ["a","b","c","d"] -- ["a","b","c","d"] -- >>> setAt 100 "hello" ["a","b","c","d"] -- ["a","b","c","d"] setAt :: forall a. Int -> a -> [a] -> [a] setAt n newVal = overAt n (const newVal) -- | Update a given value in a list. -- -- >>> overAt 2 (\x -> x ++ x) ["a","b","c","d"] -- ["a","b","cc","d"] -- -- You can update the first and last values in the list as well: -- -- >>> overAt 0 (\x -> "bye") ["a","b","c","d"] -- ["bye","b","c","d"] -- >>> overAt 3 (\x -> "") ["a","b","c","d"] -- ["a","b","c",""] -- -- If you try to set a value outside of the list, you'll get back the same -- list: -- -- >>> overAt (-10) (\_ -> "foobar") ["a","b","c","d"] -- ["a","b","c","d"] -- >>> overAt 100 (\_ -> "baz") ["a","b","c","d"] -- ["a","b","c","d"] overAt :: forall a. Int -> (a -> a) -> [a] -> [a] overAt n f = foldr go [] . zip [0..] where go :: (Int, a) -> [a] -> [a] go (i, a) next | i == n = f a : next | otherwise = a : next -- | Set a given value in a 'List8'. -- -- Internally uses 'setAt'. See documentation on 'setAt' for some examples. setAtList8 :: Int -> a -> List8 a -> List8 a setAtList8 n a (List8 l) = List8 (setAt n a l) -- | Set a given value in a 'List8'. -- -- Internally uses 'overAt'. See documentation on 'overAt' for some examples. overAtList8 :: Int -> (a -> a) -> List8 a -> List8 a overAtList8 n f (List8 l) = List8 (overAt n f l) -- | This newtype is for length 6 lists. Construct it with 'mkList6' or 'unsafeMkList6' newtype List6 a = List6 { getList6 :: [a] } deriving (Show, Eq, Foldable, Functor) -- | Typesafe smart constructor for length 6 lists. mkList6 :: [a] -> Maybe (List6 a) mkList6 xs = if length xs == 6 then Just (List6 xs) else Nothing -- | Unsafe smart constructor for length 6 lists. unsafeMkList6 :: [a] -> List6 a unsafeMkList6 xs = case mkList6 xs of Just xs' -> xs' Nothing -> error $ "unsafeMkList6: input list contains " <> show (length xs) <> " elements. Must contain exactly 6 elements." -- | Set a given value in a 'List6'. -- -- Internally uses 'setAt'. See documentation on 'setAt' for some examples. setAtList6 :: Int -> a -> List6 a -> List6 a setAtList6 n a (List6 l) = List6 (setAt n a l) -- | Set a given value in a 'List6'. -- -- Internally uses 'overAt'. See documentation on 'overAt' for some examples. overAtList6 :: Int -> (a -> a) -> List6 a -> List6 a overAtList6 n f (List6 l) = List6 (overAt n f l) -- | This newtype is for length 24 lists. Construct it with 'mkList24' or 'unsafeMkList24' newtype List24 a = List24 { getList24 :: [a] } deriving (Show, Eq, Foldable, Functor) -- | Typesafe smart constructor for length 24 lists. mkList24 :: [a] -> Maybe (List24 a) mkList24 xs = if length xs == 24 then Just (List24 xs) else Nothing -- | Unsafe smart constructor for length 24 lists. unsafeMkList24 :: [a] -> List24 a unsafeMkList24 xs = case mkList24 xs of Just xs' -> xs' Nothing -> error "List must contain 24 elements" -- | Set a given value in a 'List24'. -- -- Internally uses 'setAt'. See documentation on 'setAt' for some examples. setAtList24 :: Int -> a -> List24 a -> List24 a setAtList24 n a (List24 l) = List24 (setAt n a l) -- | Set a given value in a 'List24'. -- -- Internally uses 'overAt'. See documentation on 'overAt' for some examples. overAtList24 :: Int -> (a -> a) -> List24 a -> List24 a overAtList24 n f (List24 l) = List24 (overAt n f l) -- | This newtype is for 6x6x6 matrices.. Construct it with 'mkMatrix' or 'unsafeMkMatrix' newtype Matrix a = Matrix (List6 (List6 (List6 a))) deriving (Show, Eq, Functor, Foldable) getMatrix :: Matrix a -> [[[a]]] getMatrix (Matrix (List6 m)) = fmap (getList6 . fmap getList6) m -- | Unsafe smart constructor for 6x6x6 Matrices. mkMatrix :: [[[a]]] -> Maybe (Matrix a) mkMatrix xs = if length xs == 6 && all (\x -> length x == 6) xs && all (all (\x -> length x == 6)) xs then Just $ Matrix $ List6 (fmap (List6 . fmap List6) xs) else Nothing -- | Unsafe smart constructor for 6x6x6 Matrices. unsafeMkMatrix :: [[[a]]] -> Matrix a unsafeMkMatrix xs = case mkMatrix xs of Just xs' -> xs' Nothing -> error "unsafeMkMatrix: input list must be exactly 6x6x6" -- | Set a given value in a 'Matrix'. -- -- Internally uses 'setAt'. See documentation on 'setAt' for some examples. setAtMatrix :: Int -> Int -> Int -> a -> Matrix a -> Matrix a setAtMatrix x y z a = overAtMatrix x y z (const a) -- | Set a given value in a 'Matrix'. -- -- Internally uses 'overAt'. See documentation on 'overAt' for some examples. overAtMatrix :: Int -> Int -> Int -> (a -> a) -> Matrix a -> Matrix a overAtMatrix x y z f (Matrix l6) = Matrix (overAtList6 x (overAtList6 y (overAtList6 z f)) l6) -- | This is the color palette to use for the terminal. Each data constructor -- lets you set progressively more colors. These colors are used by the -- terminal to render -- . -- -- There are 256 total terminal colors. 'BasicPalette' lets you set the first 8, -- 'ExtendedPalette' lets you set the first 16, 'ColourCubePalette' lets you set -- the first 232, and 'FullPalette' lets you set all 256. -- -- The first 8 colors codes are the standard colors. The next 8 are the -- extended (light) colors. The next 216 are a full color cube. The last 24 are a -- grey scale. -- -- The following image gives an idea of what each individual color looks like: -- -- <> -- -- This picture does not exactly match up with Termonad's default colors, but it gives an -- idea of what each block of colors represents. -- -- You can use 'defaultStandardColours', 'defaultLightColours', -- 'defaultColourCube', and 'defaultGreyscale' as a starting point to -- customize the colors. The only time you'd need to use a constructor other -- than 'NoPalette' is when you want to customize the default colors. -- That is to say, using 'FullPalette' with all the defaults should give you the -- same result as using 'NoPalette'. data Palette c = NoPalette -- ^ Don't set any colors and just use the default from VTE. This is a black -- background with light grey text. | BasicPalette !(List8 c) -- ^ Set the colors from the standard colors. | ExtendedPalette !(List8 c) !(List8 c) -- ^ Set the colors from the extended (light) colors (as well as standard colors). | ColourCubePalette !(List8 c) !(List8 c) !(Matrix c) -- ^ Set the colors from the color cube (as well as the standard colors and -- extended colors). | FullPalette !(List8 c) !(List8 c) !(Matrix c) !(List24 c) -- ^ Set the colors from the grey scale (as well as the standard colors, -- extended colors, and color cube). deriving (Eq, Show, Functor, Foldable) -- | Convert a 'Palette' to a list of colors. This is helpful for debugging. paletteToList :: Palette c -> [c] paletteToList = Data.Foldable.toList -- | Create a vector of colors based on input bits. -- -- This is used to derive 'defaultStandardColours' and 'defaultLightColours'. -- -- >>> coloursFromBits 192 0 == defaultStandardColours -- True -- -- >>> coloursFromBits 192 63 == defaultLightColours -- True -- -- In general, as an end-user, you shouldn't need to use this. coloursFromBits :: forall b. (Ord b, Floating b) => Word8 -> Word8 -> List8 (AlphaColour b) coloursFromBits scale offset = genList createElem where createElem :: Int -> AlphaColour b createElem i = let red = cmp 0 i green = cmp 1 i blue = cmp 2 i color = opaque $ sRGB24 red green blue in color cmp :: Int -> Int -> Word8 cmp i = (offset +) . (scale *) . fromIntegral . bit i bit :: Int -> Int -> Int bit m i = i `div` (2 ^ m) `mod` 2 genList :: (Int -> a) -> List8 a genList f = unsafeMkList8 [ f x | x <- [0..7]] -- | A 'Vec' of standard colors. Default value for 'BasicPalette'. -- -- >>> showColourVec defaultStandardColours -- ["#000000ff","#c00000ff","#00c000ff","#c0c000ff","#0000c0ff","#c000c0ff","#00c0c0ff","#c0c0c0ff"] defaultStandardColours :: (Ord b, Floating b) => List8 (AlphaColour b) defaultStandardColours = coloursFromBits 192 0 -- | A 'Vec' of extended (light) colors. Default value for 'ExtendedPalette'. -- -- >>> showColourVec defaultLightColours -- ["#3f3f3fff","#ff3f3fff","#3fff3fff","#ffff3fff","#3f3fffff","#ff3fffff","#3fffffff","#ffffffff"] defaultLightColours :: (Ord b, Floating b) => List8 (AlphaColour b) defaultLightColours = coloursFromBits 192 63 -- | Convert an 'AlphaColour' to a 'Colour'. -- -- >>> sRGB24show $ pureColour (opaque green) -- "#008000" -- >>> sRGB24show $ pureColour (sRGB32 0x30 0x40 0x50 0x80) -- "#304050" -- -- We assume that black is the pure color for a fully transparent -- 'AlphaColour'. -- -- >>> sRGB24show $ pureColour transparent -- "#000000" -- -- This function has been taken from: -- https://wiki.haskell.org/Colour#Getting_semi-transparent_coordinates pureColour :: AlphaColour Double -> Colour Double pureColour alaphaColour | a > 0 = darken (recip a) (alaphaColour `over` black) | otherwise = black where a :: Double a = alphaChannel alaphaColour -- | 'round's and then clamps the input between 0 and 'maxBound'. -- -- Rounds the input: -- -- >>> quantize (100.2 :: Double) :: Word8 -- 100 -- -- Clamps to 'minBound' if input is too low: -- -- >>> quantize (-3 :: Double) :: Word8 -- 0 -- -- Clamps to 'maxBound' if input is too high: -- >>> quantize (1000 :: Double) :: Word8 -- 255 -- -- Function used to quantize the alpha channel in the same way as the 'RGB' -- components. It has been copied from "Data.Colour.Internal". quantize :: forall a b. (RealFrac a, Integral b, Bounded b) => a -> b quantize x | x <= fromIntegral l = l | fromIntegral h <= x = h | otherwise = round x where l :: b l = minBound h :: b h = maxBound -- | Show an 'AlphaColour' in hex. -- -- >>> sRGB32show (opaque red) -- "#ff0000ff" -- -- Similar to 'Data.Colour.SRGB.sRGB24show'. sRGB32show :: AlphaColour Double -> String sRGB32show c = printf "#%02x%02x%02x%02x" r g b a where r, g, b :: Word8 RGB r g b = toSRGB24 $ pureColour c -- This about the same code as in Data.Colour.SRGB.toSRGBBounded a :: Word8 a = quantize (255 * alphaChannel c) -- | Create an 'AlphaColour' from a four 'Word8's. -- -- >>> sRGB32show $ sRGB32 64 96 128 255 -- "#406080ff" -- >>> sRGB32show $ sRGB32 0x08 0x10 0x20 0x01 -- "#08102001" -- -- Note that if you specify the alpha as 0 (which means completely -- translucent), all the color channels will be set to 0 as well. -- -- >>> sRGB32show $ sRGB32 100 150 200 0 -- "#00000000" -- -- Similar to 'sRGB24' but also includes an alpha channel. Most users will -- probably want to use 'createColour' instead. sRGB32 :: Word8 -- ^ red channel -> Word8 -- ^ green channel -> Word8 -- ^ blue channel -> Word8 -- ^ alpha channel -> AlphaColour Double sRGB32 r g b 255 = withOpacity (sRGB24 r g b) 1 sRGB32 r g b a = let aDouble = fromIntegral a / 255 in withOpacity (sRGB24 r g b) aDouble -- | Create an 'AlphaColour' that is fully 'opaque'. -- -- >>> sRGB32show $ createColour 64 96 128 -- "#406080ff" -- >>> sRGB32show $ createColour 0 0 0 -- "#000000ff" -- -- Similar to 'sRGB24' but for 'AlphaColour'. createColour :: Word8 -- ^ red channel -> Word8 -- ^ green channel -> Word8 -- ^ blue channel -> AlphaColour Double createColour r g b = sRGB32 r g b 255 -- | A helper function for showing all the colors in 'Vec' of colors. showColourVec :: List8 (AlphaColour Double) -> [String] showColourVec (List8 xs) = fmap sRGB32show xs genMatrix :: (Int -> Int -> Int -> a) -> [a] genMatrix f = [ f x y z | x <- [0..5], y <- [0..5], z <- [0..5] ] build :: ((a -> [a] -> [a]) -> [a] -> [a]) -> [a] build g = g (:) [] chunksOf :: Int -> [e] -> [[e]] chunksOf i ls = map (take i) (build (splitter ls)) where splitter :: [e] -> ([e] -> a -> a) -> a -> a splitter [] _ n = n splitter l c n = l `c` splitter (drop i l) c n -- | Specify a colour cube with one colour vector for its displacement and three -- colour vectors for its edges. Produces a uniform 6x6x6 grid bounded by -- and orthognal to the faces. cube :: forall b. Fractional b => AlphaColour b -> AlphaColour b -> AlphaColour b -> AlphaColour b -> Matrix (AlphaColour b) cube d i j k = let xs = genMatrix $ \x y z -> affineCombo [(1, d), (coef x, i), (coef y, j), (coef z, k)] $ opaque black in unsafeMkMatrix $ chunksOf 6 $ chunksOf 6 xs where coef :: Int -> b coef x = fromIntegral x / 5 -- | A matrix of a 6 x 6 x 6 color cube. Default value for 'ColourCubePalette'. -- -- >>> putStrLn $ pack $ showColourCube defaultColourCube -- [ [ #000000ff, #00005fff, #000087ff, #0000afff, #0000d7ff, #0000ffff -- , #005f00ff, #005f5fff, #005f87ff, #005fafff, #005fd7ff, #005fffff -- , #008700ff, #00875fff, #008787ff, #0087afff, #0087d7ff, #0087ffff -- , #00af00ff, #00af5fff, #00af87ff, #00afafff, #00afd7ff, #00afffff -- , #00d700ff, #00d75fff, #00d787ff, #00d7afff, #00d7d7ff, #00d7ffff -- , #00ff00ff, #00ff5fff, #00ff87ff, #00ffafff, #00ffd7ff, #00ffffff -- ] -- , [ #5f0000ff, #5f005fff, #5f0087ff, #5f00afff, #5f00d7ff, #5f00ffff -- , #5f5f00ff, #5f5f5fff, #5f5f87ff, #5f5fafff, #5f5fd7ff, #5f5fffff -- , #5f8700ff, #5f875fff, #5f8787ff, #5f87afff, #5f87d7ff, #5f87ffff -- , #5faf00ff, #5faf5fff, #5faf87ff, #5fafafff, #5fafd7ff, #5fafffff -- , #5fd700ff, #5fd75fff, #5fd787ff, #5fd7afff, #5fd7d7ff, #5fd7ffff -- , #5fff00ff, #5fff5fff, #5fff87ff, #5fffafff, #5fffd7ff, #5fffffff -- ] -- , [ #870000ff, #87005fff, #870087ff, #8700afff, #8700d7ff, #8700ffff -- , #875f00ff, #875f5fff, #875f87ff, #875fafff, #875fd7ff, #875fffff -- , #878700ff, #87875fff, #878787ff, #8787afff, #8787d7ff, #8787ffff -- , #87af00ff, #87af5fff, #87af87ff, #87afafff, #87afd7ff, #87afffff -- , #87d700ff, #87d75fff, #87d787ff, #87d7afff, #87d7d7ff, #87d7ffff -- , #87ff00ff, #87ff5fff, #87ff87ff, #87ffafff, #87ffd7ff, #87ffffff -- ] -- , [ #af0000ff, #af005fff, #af0087ff, #af00afff, #af00d7ff, #af00ffff -- , #af5f00ff, #af5f5fff, #af5f87ff, #af5fafff, #af5fd7ff, #af5fffff -- , #af8700ff, #af875fff, #af8787ff, #af87afff, #af87d7ff, #af87ffff -- , #afaf00ff, #afaf5fff, #afaf87ff, #afafafff, #afafd7ff, #afafffff -- , #afd700ff, #afd75fff, #afd787ff, #afd7afff, #afd7d7ff, #afd7ffff -- , #afff00ff, #afff5fff, #afff87ff, #afffafff, #afffd7ff, #afffffff -- ] -- , [ #d70000ff, #d7005fff, #d70087ff, #d700afff, #d700d7ff, #d700ffff -- , #d75f00ff, #d75f5fff, #d75f87ff, #d75fafff, #d75fd7ff, #d75fffff -- , #d78700ff, #d7875fff, #d78787ff, #d787afff, #d787d7ff, #d787ffff -- , #d7af00ff, #d7af5fff, #d7af87ff, #d7afafff, #d7afd7ff, #d7afffff -- , #d7d700ff, #d7d75fff, #d7d787ff, #d7d7afff, #d7d7d7ff, #d7d7ffff -- , #d7ff00ff, #d7ff5fff, #d7ff87ff, #d7ffafff, #d7ffd7ff, #d7ffffff -- ] -- , [ #ff0000ff, #ff005fff, #ff0087ff, #ff00afff, #ff00d7ff, #ff00ffff -- , #ff5f00ff, #ff5f5fff, #ff5f87ff, #ff5fafff, #ff5fd7ff, #ff5fffff -- , #ff8700ff, #ff875fff, #ff8787ff, #ff87afff, #ff87d7ff, #ff87ffff -- , #ffaf00ff, #ffaf5fff, #ffaf87ff, #ffafafff, #ffafd7ff, #ffafffff -- , #ffd700ff, #ffd75fff, #ffd787ff, #ffd7afff, #ffd7d7ff, #ffd7ffff -- , #ffff00ff, #ffff5fff, #ffff87ff, #ffffafff, #ffffd7ff, #ffffffff -- ] -- ] defaultColourCube :: (Ord b, Floating b) => Matrix (AlphaColour b) defaultColourCube = let xs = genMatrix $ \x y z -> opaque $ sRGB24 (cmp x) (cmp y) (cmp z) in unsafeMkMatrix $ chunksOf 6 $ chunksOf 6 xs where cmp :: Int -> Word8 cmp i = let i' = fromIntegral i in signum i' * 55 + 40 * i' -- | Helper function for showing all the colors in a color cube. This is used -- for debugging. showColourCube :: Matrix (AlphaColour Double) -> String showColourCube matrix = let itemList = (mconcat . mconcat) $ getMatrix matrix in showSColourCube itemList "" where showSColourCube :: [AlphaColour Double] -> String -> String showSColourCube itemList = showString "[ " . showSquare 0 itemList . showString ", " . showSquare 1 itemList . showString ", " . showSquare 2 itemList . showString ", " . showSquare 3 itemList . showString ", " . showSquare 4 itemList . showString ", " . showSquare 5 itemList . showString "]" showSquare :: Int -> [AlphaColour Double] -> String -> String showSquare i colours = showString "[ " . showRow i 0 colours . showString ", " . showRow i 1 colours . showString ", " . showRow i 2 colours . showString ", " . showRow i 3 colours . showString ", " . showRow i 4 colours . showString ", " . showRow i 5 colours . showString "]\n" showRow :: Int -> Int -> [AlphaColour Double] -> String -> String showRow i j colours = showCol (headEx $ drop (i * 36 + j * 6 + 0) colours) . showString ", " . showCol (headEx $ drop (i * 36 + j * 6 + 1) colours) . showString ", " . showCol (headEx $ drop (i * 36 + j * 6 + 2) colours) . showString ", " . showCol (headEx $ drop (i * 36 + j * 6 + 3) colours) . showString ", " . showCol (headEx $ drop (i * 36 + j * 6 + 4) colours) . showString ", " . showCol (headEx $ drop (i * 36 + j * 6 + 5) colours) . showString "\n " showCol :: AlphaColour Double -> String -> String showCol col str = sRGB32show col <> str -- | A List of a grey scale. Default value for 'FullPalette'. -- -- >>> fmap sRGB32show defaultGreyscale -- List24 {getList24 = ["#080808ff","#121212ff","#1c1c1cff","#262626ff","#303030ff","#3a3a3aff","#444444ff","#4e4e4eff","#585858ff","#626262ff","#6c6c6cff","#767676ff","#808080ff","#8a8a8aff","#949494ff","#9e9e9eff","#a8a8a8ff","#b2b2b2ff","#bcbcbcff","#c6c6c6ff","#d0d0d0ff","#dadadaff","#e4e4e4ff","#eeeeeeff"]} defaultGreyscale :: (Ord b, Floating b) => List24 (AlphaColour b) defaultGreyscale = unsafeMkList24 $ do n <- [0..23] let l = 8 + 10 * n pure $ opaque $ sRGB24 l l l -- | The configuration for the colors used by Termonad. -- -- 'foregroundColour' and 'backgroundColour' allow you to set the color of the -- foreground text and background of the terminal. -- -- 'highlightFgColour' and 'highlightBgColour' allow you to set the color of -- the foreground and background of the highlighted text. -- -- 'palette' allows you to set the full color palette used by the terminal. -- See 'Palette' for more information. -- -- If you don't set 'foregroundColour', 'backgroundColour', 'highlightFgColour', -- 'highlightBgColour', or 'palette', the defaults from VTE are used. -- -- If you want to use a terminal with a white (or light) background and a black -- foreground, it may be a good idea to change some of the colors in the -- 'Palette' as well. -- -- VTE works as follows: if you don't explicitly set a background or foreground color, -- it takes the 0th colour from the 'palette' to be the background color, and the 7th -- colour from the 'palette' to be the foreground color. If you notice oddities with -- colouring in certain applications, it may be helpful to make sure that these -- 'palette' colours match up with the 'backgroundColour' and 'foregroundColour' you -- have set.) -- -- 'cursorFgColour' and 'cursorBgColour' allow you to set the foreground color -- of the text under the cursor, as well as the color of the cursor itself. -- -- Termonad will behave differently depending on the combination -- 'cursorFgColour' and 'cursorBgColour' being 'Set' vs. 'Unset'. -- Here is the summary of the different possibilities: -- -- * 'cursorFgColour' is 'Set' and 'cursorBgColour' is 'Set' -- -- The foreground and background colors of the cursor are as you have set. -- -- * 'cursorFgColour' is 'Set' and 'cursorBgColour' is 'Unset' -- -- The cursor background color turns completely black so that it is not -- visible. The foreground color of the cursor is the color that you have -- 'Set'. This ends up being mostly unusable, so you are recommended to -- always 'Set' 'cursorBgColour' when you have 'Set' 'cursorFgColour'. -- -- * 'cursorFgColour' is 'Unset' and 'cursorBgColour' is 'Set' -- -- The cursor background color becomes the color you 'Set', while the cursor -- foreground color doesn't change from the letter it is over. For instance, -- imagine there is a letter on the screen with a black background and a -- green foreground. If you bring the cursor overtop of it, the cursor -- background will be the color you have 'Set', while the cursor foreground -- will be green. -- -- This is completely usable, but is slightly annoying if you place the cursor -- over a letter with the same foreground color as the cursor's background -- color, because the letter will not be readable. For instance, imagine you -- have set your cursor background color to red, and somewhere on the screen -- there is a letter with a black background and a red foreground. If you move -- your cursor over the letter, the background of the cursor will be red (as -- you have set), and the cursor foreground will be red (to match the original -- foreground color of the letter). This will make it so you can't -- actually read the letter, because the foreground and background are both -- red. -- -- * 'cursorFgColour' is 'Unset' and 'cursorBgColour' is 'Unset' -- -- This combination makes the cursor inverse of whatever text it is over. -- If your cursor is over red text with a black background, the cursor -- background will be red and the cursor foreground will be black. -- -- This is the default. -- -- 'cursorFgColour' is not supported in @vte-2.91@ versions older than 0.44. -- (This is somewhat confusing. Note that @vte-2.91@ is the name of the system -- library, and @0.44@ is its version number.) -- -- See 'defaultColourConfig' for the defaults for 'ColourConfig' used in Termonad. data ColourConfig c = ColourConfig { cursorFgColour :: !(Option c) -- ^ Foreground color of the cursor. This is the color of the text that -- the cursor is over. This is not supported on older versions of VTE. , cursorBgColour :: !(Option c) -- ^ Background color of the cursor. This is the color of the cursor -- itself. , foregroundColour :: !(Option c) -- ^ Color of the default foreground text in the terminal. , backgroundColour :: !(Option c) -- ^ Background color for the terminal , highlightFgColour :: !(Option c) -- ^ Foreground color for the highlighted text. , highlightBgColour :: !(Option c) -- ^ Background color for the highlighted text. , palette :: !(Palette c) -- ^ Color palette for the terminal. See 'Palette'. } deriving (Eq, Show, Functor) -- | Default setting for a 'ColourConfig'. The cursor colors, font foreground -- color, background color, highlighted text color, and color palette are all -- left at the defaults set by VTE. -- -- >>> defaultColourConfig -- ColourConfig {cursorFgColour = Unset, cursorBgColour = Unset, foregroundColour = Unset, backgroundColour = Unset, highlightFgColour = Unset, highlightBgColour = Unset, palette = NoPalette} defaultColourConfig :: ColourConfig (AlphaColour Double) defaultColourConfig = ColourConfig { cursorFgColour = Unset , cursorBgColour = Unset , foregroundColour = Unset , backgroundColour = Unset , highlightFgColour = Unset , highlightBgColour = Unset , palette = NoPalette } $(makeLensesFor [ ("cursorFgColour", "lensCursorFgColour") , ("cursorBgColour", "lensCursorBgColour") , ("foregroundColour", "lensForegroundColour") , ("backgroundColour", "lensBackgroundColour") , ("highlightFgColour", "lensHighlightFgColour") , ("highlightBgColour", "lensHighlightBgColour") , ("palette", "lensPalette") ] ''ColourConfig ) ------------------------------ -- ConfigExtension Instance -- ------------------------------ -- | Extension that allows setting colors for terminals in Termonad. data ColourExtension = ColourExtension { colourExtConf :: MVar (ColourConfig (AlphaColour Double)) -- ^ 'MVar' holding the current 'ColourConfig'. This could potentially be -- passed to other extensions or user code. This would allow changing the -- colors for new terminals in realtime. , colourExtCreateTermHook :: TMState -> Terminal -> IO () -- ^ The 'createTermHook' used by the 'ColourExtension'. This sets the -- colors for a new terminal based on the 'ColourConfig' in 'colourExtConf'. } -- | The default 'createTermHook' for 'colourExtCreateTermHook'. Set the colors -- for a terminal based on the given 'ColourConfig'. colourHook :: MVar (ColourConfig (AlphaColour Double)) -> TMState -> Terminal -> IO () colourHook mvarColourConf _ vteTerm = do colourConf <- readMVar mvarColourConf let paletteColourList = paletteToList $ palette colourConf rgbaPaletteColourList <- traverse colourToRgba paletteColourList terminalSetColors vteTerm Nothing Nothing (Just rgbaPaletteColourList) whenSet (backgroundColour colourConf) $ terminalSetColorBackground vteTerm <=< colourToRgba whenSet (foregroundColour colourConf) $ terminalSetColorForeground vteTerm <=< colourToRgba whenSet (cursorBgColour colourConf) $ terminalSetColorCursor vteTerm . Just <=< colourToRgba #ifdef VTE_VERSION_GEQ_0_44 whenSet (cursorFgColour colourConf) $ terminalSetColorCursorForeground vteTerm . Just <=< colourToRgba #endif whenSet (highlightFgColour colourConf) $ terminalSetColorHighlightForeground vteTerm . Just <=< colourToRgba whenSet (highlightBgColour colourConf) $ terminalSetColorHighlight vteTerm . Just <=< colourToRgba colourToRgba :: AlphaColour Double -> IO RGBA colourToRgba colour = do let RGB red green blue = toSRGB $ pureColour colour alpha = alphaChannel colour rgba <- newZeroRGBA setRGBARed rgba red setRGBAGreen rgba green setRGBABlue rgba blue setRGBAAlpha rgba alpha pure rgba -- | Create a 'ColourExtension' based on a given 'ColourConfig'. -- -- Most users will want to use this. createColourExtension :: ColourConfig (AlphaColour Double) -> IO ColourExtension createColourExtension conf = do mvarConf <- newMVar conf pure $ ColourExtension { colourExtConf = mvarConf , colourExtCreateTermHook = colourHook mvarConf } -- | Create a 'ColourExtension' based on 'defaultColourConfig'. -- -- Note that this is not needed if you just want to use the default colors for -- Termonad. However, if you want to pass around the 'MVar' 'ColourConfig' for -- extensions to use, then you may need this function. createDefColourExtension :: IO ColourExtension createDefColourExtension = createColourExtension defaultColourConfig -- | Add a given 'ColourConfig' to a 'TMConfig'. This adds 'colourHook' to the -- 'createTermHook' in 'TMConfig'. addColourConfig :: TMConfig -> ColourConfig (AlphaColour Double) -> IO TMConfig addColourConfig tmConf colConf = do ColourExtension _ newHook <- createColourExtension colConf let newTMConf = tmConf & lensHooks . lensCreateTermHook %~ addColourHook newHook pure newTMConf -- | This is similar to 'addColourConfig', but can be used on a -- 'ColourExtension' created with 'createColourExtension'. addColourExtension :: TMConfig -> ColourExtension -> TMConfig addColourExtension tmConf (ColourExtension _ newHook) = tmConf & lensHooks . lensCreateTermHook %~ addColourHook newHook -- | This function shows how to combine 'createTermHook's. -- -- This first runs the old hook, followed by the new hook. -- -- This is used internally by 'addColourConfig' and 'addColourExtension'. addColourHook :: (TMState -> Terminal -> IO ()) -- ^ New hook -> (TMState -> Terminal -> IO ()) -- ^ Old hook -> TMState -> Terminal -> IO () addColourHook newHook oldHook tmState term = do oldHook tmState term newHook tmState term termonad-4.5.0.0/src/Termonad/Gtk.hs0000644000000000000000000000621007346545000015300 0ustar0000000000000000{-# LANGUAGE CPP #-} -- | This module contains two things: -- -- 1. Extension functions to libraries like GTK. These functions wrap up some -- generic GTK functionality. They are not Termonad-specific. -- -- 2. Wrappers around functionality that is only specific to certain versions -- of libraries like GTK or VTE. -- -- For instance, 'terminalSetEnableSixelIfExists' is -- a wrapper around 'terminalSetEnableSixel'. Sixel support is only availble in -- vte >= 0.63, so if a user tries to compile Termonad with a version of vte -- less than 0.63, this function won't do anything. module Termonad.Gtk where import Termonad.Prelude import Control.Monad.Fail (MonadFail, fail) import Data.GI.Base (ManagedPtr, withManagedPtr) import GHC.Stack (HasCallStack) import GI.Gdk ( GObject , castTo ) import GI.Gio (ApplicationFlags) import GI.Gtk (Application, IsWidget, Widget(Widget), applicationNew, builderGetObject, toWidget) import qualified GI.Gtk as Gtk import GI.Vte ( IsTerminal #ifdef VTE_VERSION_GEQ_0_63 , terminalSetEnableSixel #endif ) objFromBuildUnsafe :: GObject o => Gtk.Builder -> Text -> (ManagedPtr o -> o) -> IO o objFromBuildUnsafe builder name constructor = do maybePlainObj <- builderGetObject builder name case maybePlainObj of Nothing -> error $ "Couldn't get " <> unpack name <> " from builder!" Just plainObj -> do maybeNewObj <- castTo constructor plainObj case maybeNewObj of Nothing -> error $ "Got " <> unpack name <> " from builder, but couldn't convert to object!" Just obj -> pure obj -- | Unsafely creates a new 'Application'. This calls 'fail' if it cannot -- create the 'Application' for some reason. -- -- This can fail for different reasons, one of which being that application -- name does not have a period in it. appNew :: (HasCallStack, MonadIO m, MonadFail m) => Maybe Text -- ^ The application name. Must have a period in it if specified. If passed -- as 'Nothing', then no application name will be used. -> [ApplicationFlags] -> m Application appNew appName appFlags = do maybeApp <- applicationNew appName appFlags case maybeApp of Nothing -> fail "Could not create application for some reason!" Just app -> pure app -- | Tests to see if two GTK widgets point to the same thing. This should only -- happen if they are actually the same thing. widgetEq :: (MonadIO m, IsWidget a, IsWidget b) => a -> b -> m Bool widgetEq a b = do Widget managedPtrA <- toWidget a Widget managedPtrB <- toWidget b liftIO $ withManagedPtr managedPtrA $ \ptrA -> withManagedPtr managedPtrB $ \ptrB -> pure (ptrA == ptrB) -- | Wrapper around 'terminalSetEnableSixel'. The 'terminalSetEnableSixel' function -- is only available starting with vte-0.63. This function has no effect when -- compiling against previous versions of vte. terminalSetEnableSixelIfExists :: (HasCallStack, MonadIO m, IsTerminal t) => t -- ^ a Terminal -> Bool -- ^ whether to enable SIXEL images -> m () terminalSetEnableSixelIfExists t b = do #ifdef VTE_VERSION_GEQ_0_63 terminalSetEnableSixel t b #endif pure () termonad-4.5.0.0/src/Termonad/Keys.hs0000644000000000000000000000613507346545000015474 0ustar0000000000000000 module Termonad.Keys where import Termonad.Prelude import Control.Lens (imap) import GI.Gdk ( EventKey , pattern KEY_0 , pattern KEY_1 , pattern KEY_2 , pattern KEY_3 , pattern KEY_4 , pattern KEY_5 , pattern KEY_6 , pattern KEY_7 , pattern KEY_8 , pattern KEY_9 , ModifierType(..) , getEventKeyHardwareKeycode , getEventKeyIsModifier , getEventKeyKeyval , getEventKeyLength , getEventKeyState , getEventKeyString , getEventKeyType ) import Termonad.Term (altNumSwitchTerm) import Termonad.Types (TMState) showKeys :: EventKey -> IO Bool showKeys eventKey = do eventType <- getEventKeyType eventKey maybeString <- getEventKeyString eventKey modifiers <- getEventKeyState eventKey len <- getEventKeyLength eventKey keyval <- getEventKeyKeyval eventKey isMod <- getEventKeyIsModifier eventKey keycode <- getEventKeyHardwareKeycode eventKey putStrLn "key press event:" putStrLn $ " type = " <> tshow eventType putStrLn $ " str = " <> tshow maybeString putStrLn $ " mods = " <> tshow modifiers putStrLn $ " isMod = " <> tshow isMod putStrLn $ " len = " <> tshow len putStrLn $ " keyval = " <> tshow keyval putStrLn $ " keycode = " <> tshow keycode putStrLn "" pure True data Key = Key { keyVal :: Word32 , keyMods :: Set ModifierType } deriving (Eq, Ord, Show) toKey :: Word32 -> Set ModifierType -> Key toKey = Key keyMap :: Map Key (TMState -> IO Bool) keyMap = let numKeys = [ KEY_1 , KEY_2 , KEY_3 , KEY_4 , KEY_5 , KEY_6 , KEY_7 , KEY_8 , KEY_9 , KEY_0 ] altNumKeys = imap (\i k -> (toKey k [ModifierTypeMod1Mask], stopProp (altNumSwitchTerm i)) ) numKeys in mapFromList altNumKeys stopProp :: (TMState -> IO a) -> TMState -> IO Bool stopProp callback terState = callback terState $> True removeStrangeModifiers :: Key -> Key removeStrangeModifiers Key{keyVal, keyMods} = let reservedModifiers = [ ModifierTypeModifierReserved13Mask , ModifierTypeModifierReserved14Mask , ModifierTypeModifierReserved15Mask , ModifierTypeModifierReserved16Mask , ModifierTypeModifierReserved17Mask , ModifierTypeModifierReserved18Mask , ModifierTypeModifierReserved19Mask , ModifierTypeModifierReserved20Mask , ModifierTypeModifierReserved21Mask , ModifierTypeModifierReserved22Mask , ModifierTypeModifierReserved23Mask , ModifierTypeModifierReserved24Mask , ModifierTypeModifierReserved25Mask , ModifierTypeModifierReserved29Mask ] in Key keyVal (difference keyMods reservedModifiers) handleKeyPress :: TMState -> EventKey -> IO Bool handleKeyPress terState eventKey = do -- void $ showKeys eventKey keyval <- getEventKeyKeyval eventKey modifiers <- getEventKeyState eventKey let oldKey = toKey keyval (setFromList modifiers) newKey = removeStrangeModifiers oldKey maybeAction = lookup newKey keyMap case maybeAction of Just action -> action terState Nothing -> pure False termonad-4.5.0.0/src/Termonad/Lenses.hs0000644000000000000000000000344407346545000016012 0ustar0000000000000000{-# LANGUAGE TemplateHaskell #-} module Termonad.Lenses where import Control.Lens (makeLensesFor, makePrisms) import Termonad.Types $(makeLensesFor [ ("term", "lensTerm") , ("pid", "lensPid") , ("unique", "lensUnique") ] ''TMTerm ) $(makeLensesFor [ ("tmNotebookTabTermContainer", "lensTMNotebookTabTermContainer") , ("tmNotebookTabTerm", "lensTMNotebookTabTerm") , ("tmNotebookTabLabel", "lensTMNotebookTabLabel") ] ''TMNotebookTab ) $(makeLensesFor [ ("tmNotebook", "lensTMNotebook") , ("tmNotebookTabs", "lensTMNotebookTabs") ] ''TMNotebook ) $(makeLensesFor [ ("tmStateApp", "lensTMStateApp") , ("tmStateAppWin", "lensTMStateAppWin") , ("tmStateNotebook", "lensTMStateNotebook") , ("tmStateFontDesc", "lensTMStateFontDesc") , ("tmStateConfig", "lensTMStateConfig") , ("tmStateUserReqExit", "lensTMStateUserReqExit") ] ''TMState' ) $(makePrisms ''FontSize) $(makeLensesFor [ ("fontFamily", "lensFontFamily") , ("fontSize", "lensFontSize") ] ''FontConfig ) $(makeLensesFor [ ("fontConfig", "lensFontConfig") , ("showScrollbar", "lensShowScrollbar") , ("colourConfig", "lensColourConfig") , ("scrollbackLen", "lensScrollbackLen") , ("confirmExit", "lensConfirmExit") , ("wordCharExceptions", "lensWordCharExceptions") , ("showMenu", "lensShowMenu") , ("showTabBar", "lensShowTabBar") , ("cursorBlinkMode", "lensCursorBlinkMode") , ("boldIsBright", "lensBoldIsBright") , ("allowBold", "lensAllowBold") , ("enableSixel", "lensEnableSixel") ] ''ConfigOptions ) $(makeLensesFor [ ("createTermHook", "lensCreateTermHook") ] ''ConfigHooks ) $(makeLensesFor [ ("options", "lensOptions") , ("hooks", "lensHooks") ] ''TMConfig ) termonad-4.5.0.0/src/Termonad/Pcre.hs0000644000000000000000000000046707346545000015454 0ustar0000000000000000{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} module Termonad.Pcre where import Foreign.C (CUInt) import qualified Language.C.Inline as C C.verbatim "#define PCRE2_CODE_UNIT_WIDTH 0" C.include "" pcre2Multiline :: CUInt pcre2Multiline = [C.pure| unsigned int { PCRE2_MULTILINE } |] termonad-4.5.0.0/src/Termonad/PreferencesFile.hs0000644000000000000000000001647007346545000017625 0ustar0000000000000000{-# LANGUAGE CPP #-} module Termonad.PreferencesFile where import Termonad.Prelude import Control.Monad.Trans.Except (ExceptT(..), runExceptT, throwE, withExceptT) import Data.Aeson (Result(..), fromJSON) #if MIN_VERSION_aeson(2, 0, 0) import qualified Data.Aeson.KeyMap as KeyMap #endif import qualified Data.HashMap.Strict as HashMap import Data.Yaml (ParseException, ToJSON (toJSON), decodeFileEither, encode, prettyPrintParseException) import Data.Yaml.Aeson (Value(..)) import System.Directory ( XdgDirectory(XdgConfig) , createDirectoryIfMissing , doesFileExist , getXdgDirectory ) import Termonad.Types ( ConfigOptions , TMConfig(TMConfig, hooks, options) , defaultConfigHooks , defaultConfigOptions ) -- $setup -- -- >>> import Data.Aeson(object, (.=)) -- | Get the path to the preferences file @~\/.config\/termonad\/termonad.yaml@. getPreferencesFile :: IO FilePath getPreferencesFile = do -- Get the termonad config directory confDir <- getXdgDirectory XdgConfig "termonad" createDirectoryIfMissing True confDir pure $ confDir "termonad.yaml" -- | Read the configuration for the preferences file -- @~\/.config\/termonad\/termonad.yaml@. This file stores only the 'options' of -- 'TMConfig' so 'hooks' are initialized with 'defaultConfigHooks'. If the -- file doesn't exist, create it with the default values. -- -- Any options that do not exist will get initialized with values from -- 'defaultConfigOptions'. tmConfigFromPreferencesFile :: IO TMConfig tmConfigFromPreferencesFile = do confFile <- getPreferencesFile -- If there is no preferences file we create it with the default values exists <- doesFileExist confFile unless exists $ writePreferencesFile confFile defaultConfigOptions -- Read the configuration file eitherOptions <- readFileWithDefaults confFile options <- case eitherOptions of Left err -> do hPutStrLn stderr $ "Error parsing file " <> pack confFile <> ": " <> err pure defaultConfigOptions Right options -> pure options pure TMConfig { options = options, hooks = defaultConfigHooks } -- | Read the 'ConfigOptions' out of a configuration file. -- -- Merge the raw 'ConfigOptions' with 'defaultConfigOptions'. This makes sure -- that old versions of the configuration file will still be able to be read -- even if new options are added to 'ConfigOptions' in new versions of -- Termonad. readFileWithDefaults :: FilePath -> IO (Either Text ConfigOptions) readFileWithDefaults file = runExceptT $ do -- Read the configuration file as a JSON object optsFromFile :: Value <- withExceptT parseExceptionToText . ExceptT $ decodeFileEither file let optsDefault :: Value = toJSON $ defaultConfigOptions -- Then merge it with the default options in JSON before converting it to -- a 'ConfigOptions' resultToExcept . fromJSON $ mergeObjVals optsFromFile optsDefault where parseExceptionToText :: ParseException -> Text parseExceptionToText = pack . prettyPrintParseException resultToExcept :: Result a -> ExceptT Text IO a resultToExcept (Success v) = pure v resultToExcept (Error str) = throwE (pack str) -- | Merge 'Value's recursively. -- -- This merges 'Value's recursively in 'Object' values, taking values that -- have been explicitly over the defaults. The defaults are only used if -- there is no value that has been explicitly set. -- -- For 'Array', 'String', 'Number', 'Bool', and 'Null', take the first 'Value' -- (the one that has been explicitly set in the user's config file): -- -- >>> mergeObjVals (Array [Number 1, Number 2]) (Array [String "hello"]) -- Array [Number 1.0,Number 2.0] -- >>> mergeObjVals (String "hello") (String "bye") -- String "hello" -- >>> mergeObjVals (Number 1) (Number 2) -- Number 1.0 -- >>> mergeObjVals (Bool True) (Bool False) -- Bool True -- >>> mergeObjVals Null Null -- Null -- -- Note that 'Value's in 'Array's are not recursed into: -- -- >>> let obj1 = object ["hello" .= Number 2] -- >>> let obj2 = object ["hello" .= String "bye"] -- >>> mergeObjVals (Array [obj1]) (Array [obj2]) -- Array [Object (fromList [("hello",Number 2.0)])] -- -- 'Object's are recursed into. Unique keys from both Maps will be used. -- Keys that are in both Maps will be merged according to the rules above: -- -- >>> let object1 = object ["hello" .= Number 1, "bye" .= Number 100] -- >>> let object2 = object ["hello" .= Number 2, "goat" .= String "chicken"] -- >>> mergeObjVals object1 object2 -- Object (fromList [("bye",Number 100.0),("goat",String "chicken"),("hello",Number 1.0)]) -- -- 'Value's of different types will use the second 'Value': -- -- >>> mergeObjVals Null (String "bye") -- String "bye" -- >>> mergeObjVals (Bool True) (Number 2) -- Number 2.0 -- >>> mergeObjVals (Object mempty) (Bool False) -- Bool False -- mergeObjVals :: Value -- ^ Value that has been set explicitly in the User's configuration -- file. -> Value -- ^ Default value that will be used if no explicitly set value. -> Value -- ^ Merged values. mergeObjVals optsFromFile optsDefault = case (optsFromFile, optsDefault) of -- Both the options from the file and the default options are an Object -- here. Recursively merge the keys and values. (Object optsFromFileKeyMap, Object optsDefaultKeyMap) -> let #if MIN_VERSION_aeson(2, 0, 0) hashMapFromKeyMap = KeyMap.toHashMap keyMapFromHashMap = KeyMap.fromHashMap #else hashMapFromKeyMap = id keyMapFromHashMap = id #endif optsFromFileHashMap = hashMapFromKeyMap optsFromFileKeyMap optsDefaultHashMap = hashMapFromKeyMap optsDefaultKeyMap optsResultHashMap = HashMap.unionWith mergeObjVals optsFromFileHashMap optsDefaultHashMap optsResultKeyMap = keyMapFromHashMap optsResultHashMap in Object optsResultKeyMap -- Both the value from the file and the default value are the same type. -- Use the value from the file. -- -- XXX: This will end up causing readFileWithDefaults to fail if the value -- from the file is old and can no longer properly be decoded into a value -- expected by ConfigOptions. (Array fromFile, Array _) -> Array fromFile (String fromFile, String _) -> String fromFile (Number fromFile, Number _) -> Number fromFile (Bool fromFile, Bool _) -> Bool fromFile (Null, Null) -> Null -- The value from the file and the default value are different types. Just -- use the default value. (_, defVal) -> defVal writePreferencesFile :: FilePath -> ConfigOptions -> IO () writePreferencesFile confFile options = do let yaml = encode options yamlWithComment = "# DO NOT EDIT THIS FILE BY HAND!\n" <> "#\n" <> "# This file is generated automatically by the Preferences dialog\n" <> "# in Termonad. Please open the Preferences dialog if you wish to\n" <> "# modify this file.\n" <> "#\n" <> "# The settings in this file will be ignored if you have a\n" <> "# termonad.hs file in this same directory.\n\n" <> yaml writeFile confFile yamlWithComment -- | Save the configuration to the preferences file -- @~\/.config\/termonad\/termonad.yaml@ saveToPreferencesFile :: TMConfig -> IO () saveToPreferencesFile TMConfig { options = options } = do confFile <- getPreferencesFile writePreferencesFile confFile options termonad-4.5.0.0/src/Termonad/Prelude.hs0000644000000000000000000000066107346545000016157 0ustar0000000000000000module Termonad.Prelude ( module X , hPutStrLn , whenJust ) where import Control.Lens as X ((&)) import Control.Monad.Trans.Maybe as X (MaybeT(MaybeT), runMaybeT) import ClassyPrelude as X import Data.Proxy as X import qualified Data.Text.IO as TextIO whenJust :: Monoid m => Maybe a -> (a -> m) -> m whenJust = flip foldMap hPutStrLn :: MonadIO m => Handle -> Text -> m () hPutStrLn hndl = liftIO . TextIO.hPutStrLn hndl termonad-4.5.0.0/src/Termonad/Term.hs0000644000000000000000000004442707346545000015476 0ustar0000000000000000{-# LANGUAGE CPP #-} module Termonad.Term where import Termonad.Prelude import Control.Lens ((^.), (.~), set, to) import Data.Colour.SRGB (Colour, RGB(RGB), toSRGB) import Data.FocusList (appendFL, deleteFL, getFocusItemFL) import GI.Gdk ( Event (Event) , EventButton , EventKey , RGBA , getEventButtonButton , newZeroRGBA , setRGBABlue , setRGBAGreen , setRGBARed , pattern BUTTON_SECONDARY , pattern CURRENT_TIME ) import GI.Gio ( Cancellable , actionMapAddAction , menuAppend , menuNew , onSimpleActionActivate , simpleActionNew ) import GI.GLib ( SpawnFlags(SpawnFlagsDefault) ) import GI.Gtk ( Adjustment , Align(AlignFill) , ApplicationWindow , Box , Button , IconSize(IconSizeMenu) , Label , Notebook , Orientation(OrientationHorizontal) , PolicyType(PolicyTypeAlways, PolicyTypeAutomatic, PolicyTypeNever) , ReliefStyle(ReliefStyleNone) , ResponseType(ResponseTypeNo, ResponseTypeYes) , ScrolledWindow , Window , applicationGetActiveWindow , boxNew , buttonNewFromIconName , buttonSetRelief , containerAdd , dialogAddButton , dialogGetContentArea , dialogNew , dialogRun , labelNew , labelSetEllipsize , labelSetLabel , labelSetMaxWidthChars , menuAttachToWidget , menuNewFromModel , menuPopupAtPointer , notebookAppendPage , notebookDetachTab , notebookGetNPages , notebookNextPage , notebookPageNum , notebookPrevPage , notebookSetCurrentPage , notebookSetShowTabs , notebookSetTabReorderable , onButtonClicked , onWidgetButtonPressEvent , onWidgetKeyPressEvent , scrolledWindowNew , scrolledWindowSetPolicy , setWidgetMargin , showUriOnWindow , widgetDestroy , widgetGrabFocus , widgetSetCanFocus , widgetSetHalign , widgetSetHexpand , widgetShow , windowSetFocus , windowSetTransientFor ) import GI.Pango (EllipsizeMode(EllipsizeModeMiddle), FontDescription) import GI.Vte ( PtyFlags(PtyFlagsDefault) , Terminal , onTerminalChildExited , onTerminalWindowTitleChanged , regexNewForMatch , terminalGetAllowHyperlink , terminalGetWindowTitle , terminalMatchAddRegex , terminalMatchCheckEvent , terminalNew , terminalSetBoldIsBright , terminalSetCursorBlinkMode , terminalSetFont , terminalSetScrollbackLines , terminalSetWordCharExceptions , terminalSpawnSync , terminalSetAllowBold ) import System.Directory (getSymbolicLinkTarget) import System.Environment (lookupEnv) import Termonad.Gtk (terminalSetEnableSixelIfExists) import Termonad.Lenses ( lensConfirmExit , lensOptions , lensShowScrollbar , lensShowTabBar , lensTMNotebookTabLabel , lensTMNotebookTabTerm , lensTMNotebookTabTermContainer , lensTMNotebookTabs , lensTMStateApp , lensTMStateConfig , lensTMStateNotebook , lensTerm ) import Termonad.Types ( ConfigHooks(createTermHook) , ConfigOptions(scrollbackLen, wordCharExceptions, cursorBlinkMode, boldIsBright, enableSixel, allowBold) , ShowScrollbar(..) , ShowTabBar(..) , TMConfig(hooks, options) , TMNotebook , TMNotebookTab , TMState , TMState'(TMState, tmStateAppWin, tmStateConfig, tmStateFontDesc, tmStateNotebook) , TMTerm , assertInvariantTMState , createTMNotebookTab , newTMTerm , pid , tmNotebook , tmNotebookTabTerm , tmNotebookTabTermContainer , tmNotebookTabs ) import Data.Coerce (coerce) import Data.GI.Base (toManagedPtr) import Termonad.Pcre (pcre2Multiline) focusTerm :: Int -> TMState -> IO () focusTerm i mvarTMState = do note <- tmNotebook . tmStateNotebook <$> readMVar mvarTMState notebookSetCurrentPage note (fromIntegral i) altNumSwitchTerm :: Int -> TMState -> IO () altNumSwitchTerm = focusTerm termNextPage :: TMState -> IO () termNextPage mvarTMState = do note <- tmNotebook . tmStateNotebook <$> readMVar mvarTMState notebookNextPage note termPrevPage :: TMState -> IO () termPrevPage mvarTMState = do note <- tmNotebook . tmStateNotebook <$> readMVar mvarTMState notebookPrevPage note termExitFocused :: TMState -> IO () termExitFocused mvarTMState = do tmState <- readMVar mvarTMState let maybeTab = tmState ^. lensTMStateNotebook . lensTMNotebookTabs . to getFocusItemFL case maybeTab of Nothing -> pure () Just tab -> termClose tab mvarTMState termClose :: TMNotebookTab -> TMState -> IO () termClose tab mvarTMState = do tmState <- readMVar mvarTMState let confirm = tmState ^. lensTMStateConfig . lensOptions . lensConfirmExit close = if confirm then termExitWithConfirmation else termExit close tab mvarTMState termExitWithConfirmation :: TMNotebookTab -> TMState -> IO () termExitWithConfirmation tab mvarTMState = do tmState <- readMVar mvarTMState let app = tmState ^. lensTMStateApp win <- applicationGetActiveWindow app dialog <- dialogNew box <- dialogGetContentArea dialog label <- labelNew (Just "Close tab?") containerAdd box label widgetShow label setWidgetMargin label 10 void $ dialogAddButton dialog "No, do NOT close tab" (fromIntegral (fromEnum ResponseTypeNo)) void $ dialogAddButton dialog "Yes, close tab" (fromIntegral (fromEnum ResponseTypeYes)) windowSetTransientFor dialog win res <- dialogRun dialog widgetDestroy dialog case toEnum (fromIntegral res) of ResponseTypeYes -> termExit tab mvarTMState _ -> pure () termExit :: TMNotebookTab -> TMState -> IO () termExit tab mvarTMState = do detachTabAction <- modifyMVar mvarTMState $ \tmState -> do let notebook = tmStateNotebook tmState detachTabAction = notebookDetachTab (tmNotebook notebook) (tmNotebookTabTermContainer tab) let newTabs = deleteFL tab (tmNotebookTabs notebook) let newTMState = set (lensTMStateNotebook . lensTMNotebookTabs) newTabs tmState pure (newTMState, detachTabAction) detachTabAction relabelTabs mvarTMState relabelTabs :: TMState -> IO () relabelTabs mvarTMState = do TMState{tmStateNotebook} <- readMVar mvarTMState let notebook = tmNotebook tmStateNotebook tabFocusList = tmNotebookTabs tmStateNotebook foldMap (go notebook) tabFocusList where go :: Notebook -> TMNotebookTab -> IO () go notebook tmNotebookTab = do let label = tmNotebookTab ^. lensTMNotebookTabLabel scrolledWin = tmNotebookTab ^. lensTMNotebookTabTermContainer term' = tmNotebookTab ^. lensTMNotebookTabTerm . lensTerm relabelTab notebook label scrolledWin term' -- | Compute the text for a 'Label' for a GTK Notebook tab. -- -- >>> computeTabLabel 0 (Just "me@machine:~") -- "1. me@machine:~" -- -- >>> computeTabLabel 5 (Just "bash process") -- "6. bash process" -- -- >>> computeTabLabel 2 Nothing -- "3. shell" computeTabLabel :: Int -- ^ Tab number. 0 is used for the first tab, 1 for the second, etc. -> Maybe Text -- ^ A possible title for a tab. If this is 'Nothing', then the string -- @shell@ will be used. -> Text computeTabLabel pageNum maybeTitle = let title = fromMaybe "shell" maybeTitle in tshow (pageNum + 1) <> ". " <> title -- | Update the given 'Label' for a GTK Notebook tab. -- -- The new text for the label is determined by the 'computeTabLabel' function. relabelTab :: Notebook -> Label -> ScrolledWindow -> Terminal -> IO () relabelTab notebook label scrolledWin term' = do tabNum <- notebookPageNum notebook scrolledWin maybeTitle <- terminalGetWindowTitle term' let labelText = computeTabLabel (fromIntegral tabNum) maybeTitle labelSetLabel label labelText showScrollbarToPolicy :: ShowScrollbar -> PolicyType showScrollbarToPolicy ShowScrollbarNever = PolicyTypeNever showScrollbarToPolicy ShowScrollbarIfNeeded = PolicyTypeAutomatic showScrollbarToPolicy ShowScrollbarAlways = PolicyTypeAlways createScrolledWin :: TMState -> IO ScrolledWindow createScrolledWin mvarTMState = do tmState <- readMVar mvarTMState let showScrollbarVal = tmState ^. lensTMStateConfig . lensOptions . lensShowScrollbar vScrollbarPolicy = showScrollbarToPolicy showScrollbarVal scrolledWin <- scrolledWindowNew (Nothing :: Maybe Adjustment) (Nothing :: Maybe Adjustment) widgetShow scrolledWin scrolledWindowSetPolicy scrolledWin PolicyTypeAutomatic vScrollbarPolicy pure scrolledWin createNotebookTabLabel :: IO (Box, Label, Button) createNotebookTabLabel = do box <- boxNew OrientationHorizontal 5 label <- labelNew (Just "") labelSetEllipsize label EllipsizeModeMiddle labelSetMaxWidthChars label 10 widgetSetHexpand label True widgetSetHalign label AlignFill button <- buttonNewFromIconName (Just "window-close") (fromIntegral (fromEnum IconSizeMenu)) buttonSetRelief button ReliefStyleNone containerAdd box label containerAdd box button widgetSetCanFocus button False widgetSetCanFocus label False widgetSetCanFocus box False widgetShow box widgetShow label widgetShow button pure (box, label, button) setShowTabs :: TMConfig -> Notebook -> IO () setShowTabs tmConfig note = do npages <- notebookGetNPages note let shouldShowTabs = case tmConfig ^. lensOptions . lensShowTabBar of ShowTabBarIfNeeded -> npages > 1 ShowTabBarAlways -> True ShowTabBarNever -> False notebookSetShowTabs note shouldShowTabs toRGBA :: Colour Double -> IO RGBA toRGBA colour = do let RGB red green blue = toSRGB colour rgba <- newZeroRGBA setRGBARed rgba red setRGBAGreen rgba green setRGBABlue rgba blue pure rgba -- | TODO: This should probably be implemented in an external package, -- since it is a generally useful utility. -- -- It should also be implemented for windows and osx. cwdOfPid :: Int -> IO (Maybe Text) cwdOfPid pd = do #ifdef mingw32_HOST_OS pure Nothing #else #ifdef darwin_HOST_OS pure Nothing #else let pidPath = "/proc" show pd "cwd" eitherLinkTarget <- try $ getSymbolicLinkTarget pidPath case eitherLinkTarget of Left (_ :: IOException) -> pure Nothing Right linkTarget -> pure $ Just $ pack linkTarget #endif #endif -- | Get the current working directory from the shell in the focused tab of a -- notebook. -- -- Returns 'Nothing' if there is no focused tab of the notebook, or the -- current working directory could not be detected for the shell. getCWDFromFocusedTab :: TMNotebook -> IO (Maybe Text) getCWDFromFocusedTab currNote = do let maybeFocusedTab = getFocusItemFL (tmNotebookTabs currNote) case maybeFocusedTab of Nothing -> pure Nothing Just focusedNotebookTab -> do let shellPid = pid (tmNotebookTabTerm focusedNotebookTab) cwdOfPid shellPid -- | Create the VTE 'Terminal', set the fonts and options createAndInitVteTerm :: FontDescription -> ConfigOptions -> IO Terminal createAndInitVteTerm tmStateFontDesc curOpts = do vteTerm <- terminalNew terminalSetFont vteTerm (Just tmStateFontDesc) terminalSetWordCharExceptions vteTerm $ wordCharExceptions curOpts terminalSetScrollbackLines vteTerm (fromIntegral (scrollbackLen curOpts)) terminalSetCursorBlinkMode vteTerm (cursorBlinkMode curOpts) terminalSetBoldIsBright vteTerm (boldIsBright curOpts) terminalSetEnableSixelIfExists vteTerm (enableSixel curOpts) terminalSetAllowBold vteTerm (allowBold curOpts) widgetShow vteTerm pure vteTerm -- | Starts a shell in a terminal and return a new TMTerm launchShell :: Terminal -- ^ GTK 'Terminal' to spawn the shell in. -> Maybe Text -- ^ An optional path to the current working directory to start the -- shell in. If 'Nothing', use the current working directory of the -- termonad process. -> IO Int launchShell vteTerm maybeCurrDir = do -- Should probably use GI.Vte.Functions.getUserShell, but contrary to its -- documentation it raises an exception rather wrap in Maybe. mShell <- lookupEnv "SHELL" let argv = maybe ["/usr/bin/env", "bash"] pure mShell -- Launch the shell shellPid <- terminalSpawnSync vteTerm [PtyFlagsDefault] maybeCurrDir argv Nothing ([SpawnFlagsDefault] :: [SpawnFlags]) Nothing (Nothing :: Maybe Cancellable) pure (fromIntegral shellPid) -- | Add a page to the notebook and switch to it. addPage :: TMState -> TMNotebookTab -> Box -- ^ The GTK Object holding the label we want to show for the tab of the -- newly created page of the notebook. -> IO () addPage mvarTMState notebookTab tabLabelBox = do -- Append a new notebook page and update the TMState to reflect this. (note, pageIndex) <- modifyMVar mvarTMState appendNotebookPage -- Switch the current Notebook page to the the newly added page. notebookSetCurrentPage note pageIndex where appendNotebookPage :: TMState' -> IO (TMState', (Notebook, Int32)) appendNotebookPage tmState = do let notebook = tmStateNotebook tmState note = tmNotebook notebook tabs = tmNotebookTabs notebook scrolledWin = tmNotebookTabTermContainer notebookTab pageIndex <- notebookAppendPage note scrolledWin (Just tabLabelBox) notebookSetTabReorderable note scrolledWin True setShowTabs (tmState ^. lensTMStateConfig) note let newTabs = appendFL tabs notebookTab newTMState = tmState & lensTMStateNotebook . lensTMNotebookTabs .~ newTabs pure (newTMState, (note, pageIndex)) -- | Set the keyboard focus on a vte terminal setFocusOn :: ApplicationWindow -> Terminal -> IO() setFocusOn tmStateAppWin vteTerm = do widgetGrabFocus vteTerm windowSetFocus tmStateAppWin (Just vteTerm) -- | Create a new 'TMTerm', setting it up and adding it to the GTKNotebook. createTerm :: (TMState -> EventKey -> IO Bool) -- ^ Funtion for handling key presses on the terminal. -> TMState -> IO TMTerm createTerm handleKeyPress mvarTMState = do -- Check preconditions assertInvariantTMState mvarTMState -- Read needed data in TMVar TMState{tmStateAppWin, tmStateFontDesc, tmStateConfig, tmStateNotebook=currNote} <- readMVar mvarTMState -- Create a new terminal and launch a shell in it vteTerm <- createAndInitVteTerm tmStateFontDesc (options tmStateConfig) maybeCurrDir <- getCWDFromFocusedTab currNote termShellPid <- launchShell vteTerm maybeCurrDir tmTerm <- newTMTerm vteTerm termShellPid -- Create the container add the VTE term in it scrolledWin <- createScrolledWin mvarTMState containerAdd scrolledWin vteTerm -- Create the GTK widget for the Notebook tab (tabLabelBox, tabLabel, tabCloseButton) <- createNotebookTabLabel -- Create notebook state let notebookTab = createTMNotebookTab tabLabel scrolledWin tmTerm -- Add the new notebooktab to the notebook. addPage mvarTMState notebookTab tabLabelBox -- Setup the initial label for the notebook tab. This needs to happen -- after we add the new page to the notebook, so that the page can get labelled -- appropriately. relabelTab (tmNotebook currNote) tabLabel scrolledWin vteTerm -- Connect callbacks void $ onButtonClicked tabCloseButton $ termClose notebookTab mvarTMState void $ onTerminalWindowTitleChanged vteTerm $ do TMState{tmStateNotebook} <- readMVar mvarTMState let notebook = tmNotebook tmStateNotebook relabelTab notebook tabLabel scrolledWin vteTerm void $ onWidgetKeyPressEvent vteTerm $ handleKeyPress mvarTMState void $ onWidgetKeyPressEvent scrolledWin $ handleKeyPress mvarTMState void $ onWidgetButtonPressEvent vteTerm $ handleMousePress tmStateAppWin vteTerm void $ onTerminalChildExited vteTerm $ \_ -> termExit notebookTab mvarTMState -- Underline URLs so that the user can see they are right-clickable. -- -- This regex is from https://www.regextester.com/94502 -- -- TODO: Roxterm and gnome-terminal have a much more in-depth set of regexes -- for URLs and things similar to URLs. At some point it might make sense to -- switch to something like this: -- https://github.com/realh/roxterm/blob/30f1faf8be4ccac8ba12b59feb5b8f758bc65a7b/src/roxterm-regex.c -- and -- https://github.com/realh/roxterm/blob/30f1faf8be4ccac8ba12b59feb5b8f758bc65a7b/src/terminal-regex.h let regexPat = "(?:http(s)?:\\/\\/)[\\w.-]+(?:\\.[\\w\\.-]+)+[\\w\\-\\._~:/?#[\\]@!\\$&'\\(\\)\\*\\+,;=.]+" -- We must set the pcre2Multiline option, otherwise VTE prints a warning. let pcreFlags = fromIntegral pcre2Multiline regex <- regexNewForMatch regexPat (fromIntegral $ length regexPat) pcreFlags void $ terminalMatchAddRegex vteTerm regex 0 -- Put the keyboard focus on the term setFocusOn tmStateAppWin vteTerm -- Make sure the state is still right assertInvariantTMState mvarTMState -- Run user-defined hooks for modifying the newly-created VTE Terminal. createTermHook (hooks tmStateConfig) mvarTMState vteTerm pure tmTerm -- | Popup the context menu on right click handleMousePress :: ApplicationWindow -> Terminal -> EventButton -> IO Bool handleMousePress win vteTerm eventButton = do x <- terminalGetAllowHyperlink vteTerm print x button <- getEventButtonButton eventButton let rightClick = button == fromIntegral BUTTON_SECONDARY when rightClick $ do menuModel <- menuNew -- if the user right-clicked on a URL, add an option to open the URL -- in a browser (maybeUrl, _regexId) <- terminalMatchCheckEvent vteTerm (eventButtonToEvent eventButton) case maybeUrl of Nothing -> pure () Just url -> do openUrlAction <- simpleActionNew "openurl" Nothing void $ onSimpleActionActivate openUrlAction $ \_ -> showUriOnWindow (Nothing :: Maybe Window) url (fromIntegral CURRENT_TIME) -- This will add the openurl action to the Application Window's action -- map everytime the user right-clicks on a URL. It is okay to add -- actions multiple times. actionMapAddAction win openUrlAction menuAppend menuModel (Just "Open URL in browser") (Just "win.openurl") menuAppend menuModel (Just "Copy") (Just "app.copy") menuAppend menuModel (Just "Paste") (Just "app.paste") menuAppend menuModel (Just "Preferences") (Just "app.preferences") menu <- menuNewFromModel menuModel menuAttachToWidget menu vteTerm Nothing menuPopupAtPointer menu Nothing pure rightClick -- The terminalMatchCheckEvent function takes an Event, while we only -- have an EventButton. It is apparently okay to just cast an EventButton -- to an Event, since they are just pointers under the hood, and they -- are laid out the same in memory. See -- https://github.com/haskell-gi/haskell-gi/issues/109 eventButtonToEvent :: EventButton -> Event eventButtonToEvent = Event . coerce . toManagedPtr termonad-4.5.0.0/src/Termonad/Types.hs0000644000000000000000000005524007346545000015666 0ustar0000000000000000{-# OPTIONS_GHC -fno-warn-orphans #-} module Termonad.Types where import Termonad.Prelude import Control.Monad.Fail (fail) import Data.FocusList (FocusList, emptyFL, singletonFL, getFocusItemFL, lengthFL) import Data.Unique (Unique, hashUnique, newUnique) import Data.Yaml ( FromJSON(parseJSON) , ToJSON(toJSON) , Value(String) , withText ) import GI.Gtk ( Application , ApplicationWindow , IsWidget , Label , Notebook , ScrolledWindow , Widget , notebookGetCurrentPage , notebookGetNthPage , notebookGetNPages ) import GI.Pango (FontDescription) import GI.Vte (Terminal, CursorBlinkMode(..)) import Text.Pretty.Simple (pPrint) import Text.Show (ShowS, showParen, showString) import Termonad.Gtk (widgetEq) -- | A wrapper around a VTE 'Terminal'. This also stores the process ID of the -- process running on this terminal, as well as a 'Unique' that can be used for -- comparing terminals. data TMTerm = TMTerm { term :: !Terminal -- ^ The actual 'Terminal'. , pid :: !Int -- ^ The process ID of the process running in 'term'. , unique :: !Unique -- ^ A 'Unique' for comparing different 'TMTerm' for uniqueness. } instance Show TMTerm where showsPrec :: Int -> TMTerm -> ShowS showsPrec d TMTerm{..} = showParen (d > 10) $ showString "TMTerm {" . showString "term = " . showString "(GI.GTK.Terminal)" . showString ", " . showString "pid = " . showsPrec (d + 1) pid . showString ", " . showString "unique = " . showsPrec (d + 1) (hashUnique unique) . showString "}" -- | A container that holds everything in a given terminal window. The 'term' -- in the 'TMTerm' is inside the 'tmNotebookTabTermContainer' 'ScrolledWindow'. -- The notebook tab 'Label' is also available. data TMNotebookTab = TMNotebookTab { tmNotebookTabTermContainer :: !ScrolledWindow -- ^ The 'ScrolledWindow' holding the VTE 'Terminal'. , tmNotebookTabTerm :: !TMTerm -- ^ The 'Terminal' insidie the 'ScrolledWindow'. , tmNotebookTabLabel :: !Label -- ^ The 'Label' holding the title of the 'Terminal' in the 'Notebook' tab. } instance Show TMNotebookTab where showsPrec :: Int -> TMNotebookTab -> ShowS showsPrec d TMNotebookTab{..} = showParen (d > 10) $ showString "TMNotebookTab {" . showString "tmNotebookTabTermContainer = " . showString "(GI.GTK.ScrolledWindow)" . showString ", " . showString "tmNotebookTabTerm = " . showsPrec (d + 1) tmNotebookTabTerm . showString ", " . showString "tmNotebookTabLabel = " . showString "(GI.GTK.Label)" . showString "}" -- | This holds the GTK 'Notebook' containing multiple tabs of 'Terminal's. We -- keep a separate list of terminals in 'tmNotebookTabs'. data TMNotebook = TMNotebook { tmNotebook :: !Notebook -- ^ This is the GTK 'Notebook' that holds multiple tabs of 'Terminal's. , tmNotebookTabs :: !(FocusList TMNotebookTab) -- ^ A 'FocusList' containing references to each individual 'TMNotebookTab'. } instance Show TMNotebook where showsPrec :: Int -> TMNotebook -> ShowS showsPrec d TMNotebook{..} = showParen (d > 10) $ showString "TMNotebook {" . showString "tmNotebook = " . showString "(GI.GTK.Notebook)" . showString ", " . showString "tmNotebookTabs = " . showsPrec (d + 1) tmNotebookTabs . showString "}" data TMState' = TMState { tmStateApp :: !Application , tmStateAppWin :: !ApplicationWindow , tmStateNotebook :: !TMNotebook , tmStateFontDesc :: !FontDescription , tmStateConfig :: !TMConfig } instance Show TMState' where showsPrec :: Int -> TMState' -> ShowS showsPrec d TMState{..} = showParen (d > 10) $ showString "TMState {" . showString "tmStateApp = " . showString "(GI.GTK.Application)" . showString ", " . showString "tmStateAppWin = " . showString "(GI.GTK.ApplicationWindow)" . showString ", " . showString "tmStateNotebook = " . showsPrec (d + 1) tmStateNotebook . showString ", " . showString "tmStateFontDesc = " . showString "(GI.Pango.FontDescription)" . showString ", " . showString "tmStateConfig = " . showsPrec (d + 1) tmStateConfig . showString "}" type TMState = MVar TMState' instance Eq TMTerm where (==) :: TMTerm -> TMTerm -> Bool (==) = (==) `on` (unique :: TMTerm -> Unique) instance Eq TMNotebookTab where (==) :: TMNotebookTab -> TMNotebookTab -> Bool (==) = (==) `on` tmNotebookTabTerm createTMTerm :: Terminal -> Int -> Unique -> TMTerm createTMTerm trm pd unq = TMTerm { term = trm , pid = pd , unique = unq } newTMTerm :: Terminal -> Int -> IO TMTerm newTMTerm trm pd = createTMTerm trm pd <$> newUnique getFocusedTermFromState :: TMState -> IO (Maybe Terminal) getFocusedTermFromState mvarTMState = withMVar mvarTMState go where go :: TMState' -> IO (Maybe Terminal) go tmState = do let maybeNotebookTab = getFocusItemFL $ tmNotebookTabs $ tmStateNotebook tmState pure $ fmap (term . tmNotebookTabTerm) maybeNotebookTab createTMNotebookTab :: Label -> ScrolledWindow -> TMTerm -> TMNotebookTab createTMNotebookTab tabLabel scrollWin trm = TMNotebookTab { tmNotebookTabTermContainer = scrollWin , tmNotebookTabTerm = trm , tmNotebookTabLabel = tabLabel } createTMNotebook :: Notebook -> FocusList TMNotebookTab -> TMNotebook createTMNotebook note tabs = TMNotebook { tmNotebook = note , tmNotebookTabs = tabs } createEmptyTMNotebook :: Notebook -> TMNotebook createEmptyTMNotebook notebook = createTMNotebook notebook emptyFL notebookToList :: Notebook -> IO [Widget] notebookToList notebook = unfoldHelper 0 [] where unfoldHelper :: Int32 -> [Widget] -> IO [Widget] unfoldHelper index32 acc = do notePage <- notebookGetNthPage notebook index32 case notePage of Nothing -> pure acc Just notePage' -> unfoldHelper (index32 + 1) (acc ++ [notePage']) newTMState :: TMConfig -> Application -> ApplicationWindow -> TMNotebook -> FontDescription -> IO TMState newTMState tmConfig app appWin note fontDesc = newMVar $ TMState { tmStateApp = app , tmStateAppWin = appWin , tmStateNotebook = note , tmStateFontDesc = fontDesc , tmStateConfig = tmConfig } newEmptyTMState :: TMConfig -> Application -> ApplicationWindow -> Notebook -> FontDescription -> IO TMState newEmptyTMState tmConfig app appWin note fontDesc = newMVar $ TMState { tmStateApp = app , tmStateAppWin = appWin , tmStateNotebook = createEmptyTMNotebook note , tmStateFontDesc = fontDesc , tmStateConfig = tmConfig } newTMStateSingleTerm :: TMConfig -> Application -> ApplicationWindow -> Notebook -> Label -> ScrolledWindow -> Terminal -> Int -> FontDescription -> IO TMState newTMStateSingleTerm tmConfig app appWin note label scrollWin trm pd fontDesc = do tmTerm <- newTMTerm trm pd let tmNoteTab = createTMNotebookTab label scrollWin tmTerm tabs = singletonFL tmNoteTab tmNote = createTMNotebook note tabs newTMState tmConfig app appWin tmNote fontDesc traceShowMTMState :: TMState -> IO () traceShowMTMState mvarTMState = do tmState <- readMVar mvarTMState print tmState ------------ -- Config -- ------------ -- | The font size for the Termonad terminal. There are two ways to set the -- fontsize, corresponding to the two different ways to set the font size in -- the Pango font rendering library. -- -- If you're not sure which to use, try 'FontSizePoints' first and see how it -- looks. It should generally correspond to font sizes you are used to from -- other applications. data FontSize = FontSizePoints Int -- ^ This sets the font size based on \"points\". The conversion between a -- point and an actual size depends on the system configuration and the -- output device. The function 'GI.Pango.fontDescriptionSetSize' is used -- to set the font size. See the documentation for that function for more -- info. | FontSizeUnits Double -- ^ This sets the font size based on \"device units\". In general, this -- can be thought of as one pixel. The function -- 'GI.Pango.fontDescriptionSetAbsoluteSize' is used to set the font size. -- See the documentation for that function for more info. deriving (Eq, FromJSON, Generic, Show, ToJSON) -- | The default 'FontSize' used if not specified. -- -- >>> defaultFontSize -- FontSizePoints 12 defaultFontSize :: FontSize defaultFontSize = FontSizePoints 12 -- | Modify a 'FontSize' by adding some value. -- -- >>> modFontSize 1 (FontSizePoints 13) -- FontSizePoints 14 -- >>> modFontSize 1 (FontSizeUnits 9.0) -- FontSizeUnits 10.0 -- -- You can reduce the font size by passing a negative value. -- -- >>> modFontSize (-2) (FontSizePoints 13) -- FontSizePoints 11 -- -- If you try to create a font size less than 1, then the old font size will be -- used. -- -- >>> modFontSize (-10) (FontSizePoints 5) -- FontSizePoints 5 -- >>> modFontSize (-1) (FontSizeUnits 1.0) -- FontSizeUnits 1.0 modFontSize :: Int -> FontSize -> FontSize modFontSize i (FontSizePoints oldPoints) = let newPoints = oldPoints + i in FontSizePoints $ if newPoints < 1 then oldPoints else newPoints modFontSize i (FontSizeUnits oldUnits) = let newUnits = oldUnits + fromIntegral i in FontSizeUnits $ if newUnits < 1 then oldUnits else newUnits -- | Settings for the font to be used in Termonad. data FontConfig = FontConfig { fontFamily :: !Text -- ^ The font family to use. Example: @"DejaVu Sans Mono"@ or @"Source Code Pro"@ , fontSize :: !FontSize -- ^ The font size. } deriving (Eq, FromJSON, Generic, Show, ToJSON) -- | The default 'FontConfig' to use if not specified. -- -- >>> defaultFontConfig == FontConfig {fontFamily = "Monospace", fontSize = defaultFontSize} -- True defaultFontConfig :: FontConfig defaultFontConfig = FontConfig { fontFamily = "Monospace" , fontSize = defaultFontSize } -- | This data type represents an option that can either be 'Set' or 'Unset'. -- -- This data type is used in situations where leaving an option unset results -- in a special state that is not representable by setting any specific value. -- -- Examples of this include the 'cursorFgColour' and 'cursorBgColour' options -- supplied by the 'ColourConfig' @ConfigExtension@. By default, -- 'cursorFgColour' and 'cursorBgColour' are both 'Unset'. However, when -- 'cursorBgColour' is 'Set', 'cursorFgColour' defaults to the color of the text -- underneath. There is no way to represent this by setting 'cursorFgColour'. data Option a = Unset | Set !a deriving (Show, Read, Eq, Ord, Functor, Foldable) -- | Run a function over the value contained in an 'Option'. Return 'mempty' -- when 'Option' is 'Unset'. -- -- >>> whenSet (Set [1,2,3]) (++ [4,5,6]) :: [Int] -- [1,2,3,4,5,6] -- >>> whenSet Unset (++ [4,5,6]) :: [Int] -- [] whenSet :: Monoid m => Option a -> (a -> m) -> m whenSet = \case Unset -> const mempty Set x -> \f -> f x -- | Whether or not to show the scroll bar in a terminal. data ShowScrollbar = ShowScrollbarNever -- ^ Never show the scroll bar, even if there are too -- many lines on the terminal to show all at once. You -- should still be able to scroll with the mouse wheel. | ShowScrollbarAlways -- ^ Always show the scrollbar, even if it is not -- needed. | ShowScrollbarIfNeeded -- ^ Only show the scrollbar if there are too many -- lines on the terminal to show all at once. deriving (Enum, Eq, Generic, FromJSON, Show, ToJSON) -- | Whether or not to show the tab bar for switching tabs. data ShowTabBar = ShowTabBarNever -- ^ Never show the tab bar, even if there are multiple tabs -- open. This may be confusing if you plan on using multiple tabs. | ShowTabBarAlways -- ^ Always show the tab bar, even if you only have one tab open. | ShowTabBarIfNeeded -- ^ Only show the tab bar if you have multiple tabs open. deriving (Enum, Eq, Generic, FromJSON, Show, ToJSON) -- | Configuration options for Termonad. -- -- See 'defaultConfigOptions' for the default values. data ConfigOptions = ConfigOptions { fontConfig :: !FontConfig -- ^ Specific options for fonts. , showScrollbar :: !ShowScrollbar -- ^ When to show the scroll bar. , scrollbackLen :: !Integer -- ^ The number of lines to keep in the scroll back history for each terminal. , confirmExit :: !Bool -- ^ Whether or not to ask you for confirmation when closing individual -- terminals or Termonad itself. It is generally safer to keep this as -- 'True'. , wordCharExceptions :: !Text -- ^ When double-clicking on text in the terminal with the mouse, Termonad -- will use this value to determine what to highlight. The individual -- characters in this list will be counted as part of a word. -- -- For instance if 'wordCharExceptions' is @""@, then when you double-click -- on the text @http://@, only the @http@ portion will be highlighted. If -- 'wordCharExceptions' is @":"@, then the @http:@ portion will be -- highlighted. , showMenu :: !Bool -- ^ Whether or not to show the @File@ @Edit@ etc menu. , showTabBar :: !ShowTabBar -- ^ When to show the tab bar. , cursorBlinkMode :: !CursorBlinkMode -- ^ How to handle cursor blink. , boldIsBright :: !Bool -- ^ This option controls whether or not to force bold text to use colors -- from the 'Termonad.Config.Colour.ExtendedPalatte'. -- -- If 'True', then colored bold text will /always/ use colors from the -- 'Termonad.Config.Colour.ExtendedPalatte'. There will be no way to print -- bold text colored with the 'Termonad.Config.Colour.BasicPalatte'. -- -- This often isn't a big problem, since many TUI applications use -- bold in combination with colors from the 'Termonad.Config.Colour.ExtendedPalatte'. -- Also, the VTE default blue color can be difficult to read with a dark -- background, and enabling this can work around the problem. -- See for more information. -- -- If 'False', then bold can be applied separately to colors from both the -- 'Termonad.Config.Colour.BasicPalatte' and -- 'Termonad.Config.Colour.ExtendedPalatte'. , enableSixel :: !Bool -- ^ Enable SIXEL to draw graphics in a terminal. -- -- In order for this option to do anything, you need to be using a version -- of VTE >= 0.63, and compile VTE with SIXEL support. -- -- Note that even if you do the above, there may still be some problems -- with SIXEL support in VTE. Follow -- for more information. , allowBold :: !Bool -- ^ Allow terminal to use bold text. -- -- You may want to disable this, for instance, if you use a font that -- doesn't look good when bold. } deriving (Eq, Generic, FromJSON, Show, ToJSON) instance FromJSON CursorBlinkMode where parseJSON = withText "CursorBlinkMode" $ \c -> do case (c :: Text) of "CursorBlinkModeSystem" -> pure CursorBlinkModeSystem "CursorBlinkModeOn" -> pure CursorBlinkModeOn "CursorBlinkModeOff" -> pure CursorBlinkModeOff _ -> fail "Wrong value for CursorBlinkMode" instance ToJSON CursorBlinkMode where toJSON CursorBlinkModeSystem = String "CursorBlinkModeSystem" toJSON CursorBlinkModeOn = String "CursorBlinkModeOn" toJSON CursorBlinkModeOff = String "CursorBlinkModeOff" -- Not supposed to happened fall back to system toJSON (AnotherCursorBlinkMode _) = String "CursorBlinkModeSystem" -- | The default 'ConfigOptions'. -- -- >>> :{ -- let defConfOpt = -- ConfigOptions -- { fontConfig = defaultFontConfig -- , showScrollbar = ShowScrollbarIfNeeded -- , scrollbackLen = 10000 -- , confirmExit = True -- , wordCharExceptions = "-#%&+,./=?@\\_~\183:" -- , showMenu = True -- , showTabBar = ShowTabBarIfNeeded -- , cursorBlinkMode = CursorBlinkModeOn -- , boldIsBright = False -- , enableSixel = False -- , allowBold = True -- } -- in defaultConfigOptions == defConfOpt -- :} -- True defaultConfigOptions :: ConfigOptions defaultConfigOptions = ConfigOptions { fontConfig = defaultFontConfig , showScrollbar = ShowScrollbarIfNeeded , scrollbackLen = 10000 , confirmExit = True , wordCharExceptions = "-#%&+,./=?@\\_~\183:" , showMenu = True , showTabBar = ShowTabBarIfNeeded , cursorBlinkMode = CursorBlinkModeOn , boldIsBright = False , enableSixel = False , allowBold = True } -- | The Termonad 'ConfigOptions' along with the 'ConfigHooks'. data TMConfig = TMConfig { options :: !ConfigOptions , hooks :: !ConfigHooks } deriving Show -- | The default 'TMConfig'. -- -- 'options' is 'defaultConfigOptions' and 'hooks' is 'defaultConfigHooks'. defaultTMConfig :: TMConfig defaultTMConfig = TMConfig { options = defaultConfigOptions , hooks = defaultConfigHooks } --------------------- -- ConfigHooks -- --------------------- -- | Hooks into certain termonad operations and VTE events. Used to modify -- termonad's behaviour in order to implement new functionality. Fields should -- have sane @Semigroup@ and @Monoid@ instances so that config extensions can -- be combined uniformly and new hooks can be added without incident. newtype ConfigHooks = ConfigHooks { -- | Produce an IO action to run on creation of new @Terminal@, given @TMState@ -- and the @Terminal@ in question. createTermHook :: TMState -> Terminal -> IO () } instance Show ConfigHooks where showsPrec :: Int -> ConfigHooks -> ShowS showsPrec _ _ = showString "ConfigHooks {" . showString "createTermHook = " . showString "}" -- | Default values for the 'ConfigHooks'. -- -- - The default function for 'createTermHook' is 'defaultCreateTermHook'. defaultConfigHooks :: ConfigHooks defaultConfigHooks = ConfigHooks { createTermHook = defaultCreateTermHook } -- | Default value for 'createTermHook'. Does nothing. defaultCreateTermHook :: TMState -> Terminal -> IO () defaultCreateTermHook _ _ = pure () ---------------- -- Invariants -- ---------------- data FocusNotSameErr = FocusListFocusExistsButNoNotebookTabWidget | NotebookTabWidgetDiffersFromFocusListFocus | NotebookTabWidgetExistsButNoFocusListFocus deriving Show data TabsDoNotMatch = TabLengthsDifferent Int Int -- ^ The first 'Int' is the number of tabs in the -- actual GTK 'Notebook'. The second 'Int' is -- the number of tabs in the 'FocusList'. | TabAtIndexDifferent Int -- ^ The tab at index 'Int' is different between -- the actual GTK 'Notebook' and the 'FocusList'. deriving (Show) data TMStateInvariantErr = FocusNotSame FocusNotSameErr Int | TabsDoNotMatch TabsDoNotMatch deriving Show -- | Gather up the invariants for 'TMState' and return them as a list. -- -- If no invariants have been violated, then this function should return an -- empty list. invariantTMState' :: TMState' -> IO [TMStateInvariantErr] invariantTMState' tmState = runInvariants [ invariantFocusSame , invariantTMTabLength , invariantTabsAllMatch ] where runInvariants :: [IO (Maybe TMStateInvariantErr)] -> IO [TMStateInvariantErr] runInvariants = fmap catMaybes . sequence invariantFocusSame :: IO (Maybe TMStateInvariantErr) invariantFocusSame = do let tmNote = tmNotebook $ tmStateNotebook tmState index32 <- notebookGetCurrentPage tmNote maybeWidgetFromNote <- notebookGetNthPage tmNote index32 let focusList = tmNotebookTabs $ tmStateNotebook tmState maybeScrollWinFromFL = tmNotebookTabTermContainer <$> getFocusItemFL focusList idx = fromIntegral index32 case (maybeWidgetFromNote, maybeScrollWinFromFL) of (Nothing, Nothing) -> pure Nothing (Just _, Nothing) -> pure $ Just $ FocusNotSame NotebookTabWidgetExistsButNoFocusListFocus idx (Nothing, Just _) -> pure $ Just $ FocusNotSame FocusListFocusExistsButNoNotebookTabWidget idx (Just widgetFromNote, Just scrollWinFromFL) -> do isEq <- widgetEq widgetFromNote scrollWinFromFL if isEq then pure Nothing else pure $ Just $ FocusNotSame NotebookTabWidgetDiffersFromFocusListFocus idx invariantTMTabLength :: IO (Maybe TMStateInvariantErr) invariantTMTabLength = do let tmNote = tmNotebook $ tmStateNotebook tmState noteLength32 <- notebookGetNPages tmNote let noteLength = fromIntegral noteLength32 focusListLength = lengthFL $ tmNotebookTabs $ tmStateNotebook tmState lengthEqual = focusListLength == noteLength if lengthEqual then pure Nothing else pure $ Just $ TabsDoNotMatch $ TabLengthsDifferent noteLength focusListLength -- Turns a FocusList and Notebook into two lists of widgets and compares each widget for equality invariantTabsAllMatch :: IO (Maybe TMStateInvariantErr) invariantTabsAllMatch = do let tmNote = tmNotebook $ tmStateNotebook tmState focusList = tmNotebookTabs $ tmStateNotebook tmState flList = tmNotebookTabTermContainer <$> toList focusList noteList <- notebookToList tmNote tabsMatch noteList flList where tabsMatch :: forall a b . (IsWidget a, IsWidget b) => [a] -> [b] -> IO (Maybe TMStateInvariantErr) tabsMatch xs ys = foldr go (pure Nothing) (zip3 xs ys [0..]) where go :: (a, b, Int) -> IO (Maybe TMStateInvariantErr) -> IO (Maybe TMStateInvariantErr) go (x, y, i) acc = do isEq <- widgetEq x y if isEq then acc else pure . Just $ TabsDoNotMatch (TabAtIndexDifferent i) -- | Check the invariants for 'TMState', and call 'fail' if we find that they -- have been violated. assertInvariantTMState :: TMState -> IO () assertInvariantTMState mvarTMState = do tmState <- readMVar mvarTMState assertValue <- invariantTMState' tmState case assertValue of [] x-> pure () errs@(_:_) -> do putStrLn "In assertInvariantTMState, some invariants for TMState are being violated." putStrLn "\nInvariants violated:" print errs putStrLn "\nTMState:" pPrint tmState putStrLn "" fail "Invariants violated for TMState" pPrintTMState :: TMState -> IO () pPrintTMState mvarTMState = do tmState <- readMVar mvarTMState pPrint tmState termonad-4.5.0.0/src/Termonad/XML.hs0000644000000000000000000002353407346545000015223 0ustar0000000000000000{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} module Termonad.XML where import Termonad.Prelude import Data.Default (def) import Data.FileEmbed (embedFile) import Text.XML (renderText) import Text.XML.QQ (Document, xmlRaw) -- TODO: A number of widgets have different places where a child can be added -- (e.g. tabs vs. page content in notebooks). This can be reflected in a UI -- definition by specifying the “type” attribute on a The possible -- values for the “type” attribute are described in the sections describing the -- widget-specific portions of UI definitions. interfaceDoc :: Document interfaceDoc = [xmlRaw| Example Application 600 400 True vertical True |] interfaceText :: Text interfaceText = toStrict $ renderText def interfaceDoc menuDoc :: Document menuDoc = [xmlRaw| File
New _Tab app.newtab
_Close Tab app.closetab _Quit app.quit
Edit _Copy app.copy _Paste app.paste _Preferences app.preferences View _Enlarge Font Size app.enlargefont _Reduce Font Size app.reducefont Search _Find... app.find Find Above app.findabove Find Below app.findbelow Help _About app.about
|] menuText :: Text menuText = toStrict $ renderText def menuDoc aboutDoc :: Document aboutDoc = [xmlRaw| About False True True 6 12 6 True _Font: True font 1 0 0 True 1 0 True _Transition: True transition 1 0 1 True None Fade Slide 1 1 |] aboutText :: Text aboutText = toStrict $ renderText def aboutDoc closeTabDoc :: Document closeTabDoc = [xmlRaw| Close Tab False True True 10 True True Close tab? 10 True True True 10 True True Yes, close tab True True GTK_RELIEF_NORMAL No, do NOT close tab True True GTK_RELIEF_NORMAL |] closeTabText :: Text closeTabText = toStrict $ renderText def closeTabDoc preferencesText :: Text preferencesText = decodeUtf8 $(embedFile "glade/preferences.glade") termonad-4.5.0.0/termonad.cabal0000644000000000000000000002553107346545000014463 0ustar0000000000000000name: termonad version: 4.5.0.0 synopsis: Terminal emulator configurable in Haskell description: Termonad is a terminal emulator configurable in Haskell. It is extremely customizable and provides hooks to modify the default behavior. It can be thought of as the \"XMonad\" of terminal emulators. Termonad was featured on an of . This video gives a short overview of Termonad. . Please see for more information. homepage: https://github.com/cdepillabout/termonad license: BSD3 license-file: LICENSE author: Dennis Gosnell maintainer: cdep.illabout@gmail.com copyright: 2017-2021 Dennis Gosnell category: Text build-type: Custom cabal-version: 1.12 extra-source-files: README.md , CHANGELOG.md , default.nix , flake.nix , flake.lock , glade/preferences.glade , glade/README.md , img/termonad.png , .nix-helpers/nixops.nix , .nix-helpers/nixpkgs.nix , .nix-helpers/overlays.nix , .nix-helpers/stack-shell.nix , .nix-helpers/termonad-with-packages.nix , shell.nix data-files: img/termonad-lambda.png custom-setup -- Hackage apparently gives an error if you don't specify an -- upper bound on your setup-depends. But I don't want to -- have to remember to bump the upper bound of each of these -- setup depends every time I want to make a release of Termonad, -- especially send I already have upper bounds on important things -- like base below. setup-depends: base <999 , Cabal <999 , cabal-doctest >=1.0.2 && <1.1 -- This flag builds the example code in the example-config/ directory, as well -- as the example from the README.md file. It is only used for testing. It -- should be enabled for CI. flag buildexamples description: Build an executable from the examples in the example-config/ directory, as well as the example from the README.md file. This is normally only used for testing. default: False library hs-source-dirs: src exposed-modules: Termonad , Termonad.App , Termonad.Config , Termonad.Config.Colour , Termonad.Gtk , Termonad.Keys , Termonad.Lenses , Termonad.Pcre , Termonad.PreferencesFile , Termonad.Prelude , Termonad.Term , Termonad.Types , Termonad.XML other-modules: Paths_termonad build-depends: base >= 4.13 && < 5 , adjunctions , aeson , classy-prelude , colour , constraints , containers , data-default , directory >= 1.3.1.0 , distributive , dyre , file-embed , filepath , focuslist , gi-gdk , gi-gio , gi-glib , gi-gtk >= 3.0.24 , gi-pango , gi-vte >= 2.91.19 , haskell-gi-base >= 0.21.2 , inline-c , lens , mono-traversable , pretty-simple , QuickCheck , text , transformers , unordered-containers , xml-conduit , xml-html-qq , yaml default-language: Haskell2010 ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates default-extensions: DataKinds , DefaultSignatures , DeriveAnyClass , DeriveFoldable , DeriveFunctor , DeriveGeneric , DerivingStrategies , EmptyCase , ExistentialQuantification , FlexibleContexts , FlexibleInstances , GADTs , GeneralizedNewtypeDeriving , InstanceSigs , KindSignatures , LambdaCase , MultiParamTypeClasses , NamedFieldPuns , NoImplicitPrelude , OverloadedLabels , OverloadedLists , OverloadedStrings , PatternSynonyms , PolyKinds , RankNTypes , RecordWildCards , ScopedTypeVariables , StandaloneDeriving , TypeApplications , TypeFamilies , TypeOperators other-extensions: TemplateHaskell , UndecidableInstances pkgconfig-depends: gtk+-3.0 , libpcre2-8 , vte-2.91 >= 0.46 executable termonad main-is: Main.hs hs-source-dirs: app build-depends: base , termonad default-language: Haskell2010 ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N test-suite doctests type: exitcode-stdio-1.0 main-is: DocTest.hs hs-source-dirs: test build-depends: base , doctest , QuickCheck , template-haskell , termonad default-language: Haskell2010 ghc-options: -Wall test-suite termonad-test type: exitcode-stdio-1.0 main-is: Test.hs hs-source-dirs: test build-depends: base , genvalidity-containers , genvalidity-hspec , hedgehog , lens , QuickCheck , termonad , tasty , tasty-hedgehog , tasty-hspec default-language: Haskell2010 ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -threaded -rtsopts -with-rtsopts=-N default-extensions: DataKinds , GADTs , GeneralizedNewtypeDeriving , InstanceSigs , KindSignatures , NamedFieldPuns , NoImplicitPrelude , OverloadedStrings , OverloadedLabels , OverloadedLists , PatternSynonyms , PolyKinds , RankNTypes , RecordWildCards , ScopedTypeVariables , TypeApplications , TypeFamilies , TypeOperators other-extensions: TemplateHaskell executable termonad-readme main-is: README.lhs hs-source-dirs: test/readme build-depends: base , markdown-unlit , termonad , colour ghc-options: -pgmL markdown-unlit default-language: Haskell2010 ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -threaded -rtsopts -with-rtsopts=-N if flag(buildexamples) buildable: True else buildable: False executable termonad-example-colour-extension main-is: example-config/ExampleColourExtension.hs build-depends: base , termonad , colour default-language: Haskell2010 ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -threaded -rtsopts -with-rtsopts=-N if flag(buildexamples) buildable: True else buildable: False executable termonad-example-colour-extension-dracula main-is: example-config/ExampleDraculaColourExtension.hs build-depends: base , termonad , colour default-language: Haskell2010 ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -threaded -rtsopts -with-rtsopts=-N if flag(buildexamples) buildable: True else buildable: False executable termonad-example-colour-extension-gruvbox main-is: example-config/ExampleGruvboxColourExtension.hs build-depends: base , termonad , colour default-language: Haskell2010 ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -threaded -rtsopts -with-rtsopts=-N if flag(buildexamples) buildable: True else buildable: False executable termonad-example-colour-extension-oneDarkPro main-is: example-config/ExampleOneDarkProColourExtension.hs build-depends: base , termonad , colour default-language: Haskell2010 ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -threaded -rtsopts -with-rtsopts=-N if flag(buildexamples) buildable: True else buildable: False executable termonad-example-colour-extension-papercolour main-is: example-config/ExamplePaperColourColourExtension.hs build-depends: base , termonad , colour default-language: Haskell2010 ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -threaded -rtsopts -with-rtsopts=-N if flag(buildexamples) buildable: True else buildable: False executable termonad-example-colour-extension-solarized main-is: example-config/ExampleSolarizedColourExtension.hs build-depends: base , termonad , colour default-language: Haskell2010 ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -threaded -rtsopts -with-rtsopts=-N if flag(buildexamples) buildable: True else buildable: False source-repository head type: git location: git@github.com:cdepillabout/termonad.git termonad-4.5.0.0/test/0000755000000000000000000000000007346545000012637 5ustar0000000000000000termonad-4.5.0.0/test/DocTest.hs0000644000000000000000000000032607346545000014541 0ustar0000000000000000 module Main where import Build_doctests (flags, pkgs, module_sources) import Test.DocTest (doctest) main :: IO () main = do doctest args where args :: [String] args = flags ++ pkgs ++ module_sources termonad-4.5.0.0/test/Test.hs0000644000000000000000000000041607346545000014113 0ustar0000000000000000 module Main where import Termonad.Prelude import Test.Tasty (TestTree, defaultMain, testGroup) main :: IO () main = do tests <- testsIO defaultMain tests testsIO :: IO TestTree testsIO = do pure $ testGroup "tests" [ -- focusListTests ] termonad-4.5.0.0/test/readme/0000755000000000000000000000000007346545000014074 5ustar0000000000000000termonad-4.5.0.0/test/readme/README.lhs0000644000000000000000000005233707346545000015553 0ustar0000000000000000 Termonad ========= [![CI](https://github.com/cdepillabout/termonad/actions/workflows/ci.yml/badge.svg)](https://github.com/cdepillabout/termonad/actions/workflows/ci.yml) [![Hackage](https://img.shields.io/hackage/v/termonad.svg)](https://hackage.haskell.org/package/termonad) [![Stackage LTS](http://stackage.org/package/termonad/badge/lts)](http://stackage.org/lts/package/termonad) [![Stackage Nightly](http://stackage.org/package/termonad/badge/nightly)](http://stackage.org/nightly/package/termonad) [![BSD3 license](https://img.shields.io/badge/license-BSD3-blue.svg)](./LICENSE) [![Join the chat at https://gitter.im/termonad/Lobby](https://badges.gitter.im/termonad/Lobby.svg)](https://gitter.im/termonad/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Termonad is a terminal emulator configurable in Haskell. It is extremely customizable and provides hooks to modify the default behavior. It can be thought of as the "XMonad" of terminal emulators. ![image of Termonad](./img/termonad.png) Termonad was [featured on an episode](https://www.youtube.com/watch?v=TLNr_gBv5HY) of [DistroTube](https://www.youtube.com/channel/UCVls1GmFKf6WlTraIb_IaJg). This video gives a short overview of Termonad. **Table of Contents** - [Termonad](#termonad) - [Installation](#installation) - [Arch Linux](#arch-linux) - [Ubuntu / Debian](#ubuntu--debian) - [Nix](#nix) - [Mac OS X](#mac-os-x) - [Installing with just `stack`](#installing-with-just-stack) - [Installing with just `nix`](#installing-with-just-nix) - [Installing with `stack` using `nix`](#installing-with-stack-using-nix) - [Windows](#windows) - [How to use Termonad](#how-to-use-termonad) - [Default Key Bindings](#default-key-bindings) - [Configuring Termonad](#configuring-termonad) - [Compiling Local Settings](#compiling-local-settings) - [Running with `stack`](#running-with-stack) - [Running with `nix`](#running-with-nix) - [Goals](#goals) - [Where to get help](#where-to-get-help) - [Contributions](#contributions) - [Maintainers](#maintainers) ## Installation Termonad can be installed on any system as long as the necessary GTK libraries are available. The following are instructions for installing Termonad on a few different distributions and systems. If the given steps don't work for you, or you want to add instructions for an additional system, please send a pull request. The following steps use the [`stack`](https://docs.haskellstack.org/en/stable/README/) build tool to build Termonad, but [`cabal`](https://www.haskell.org/cabal/) can be used as well. Steps for installing `stack` can be found on [this page](https://docs.haskellstack.org/en/stable/install_and_upgrade/). ### Arch Linux First, you must install the required Gnome/GTK system libraries: ```sh $ pacman -S vte3 gobject-introspection ``` In order to install Termonad, clone this repository and run `stack install`. This will install the `termonad` binary to `~/.local/bin/`: ```sh $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ stack install ``` Note that Termonad depends on the [`haskell-gi`](https://github.com/haskell-gi/haskell-gi) family of Haskell libraries. `haskell-gi` contains Haskell wrappers for for Gnome/GTK system libraries. It uses the [GObject Introspection](https://gi.readthedocs.io/en/latest/) functionality from the Gnome libraries. One problem that Arch users often run into is that their system Gnome/GTK libraries are newer than what the `haskell-gi` dependencies from Stackage support. If you run into this problem, there are a couple things you can try: - Manually switch to a newer Stackage resolver (probably Stackage Nightly). Newer Stackage resolvers often have newer versions of the `haskell-gi` libraries. Newer versions of the `haskell-gi` libraries are more likely to support your newer system Gnome/GTK libraries. If you get something working like this, please open a PR. - Use `cabal` for building Termonad instead of `stack`. Make sure `cabal`'s constraint solver picks the latest versions of the `haskell-gi` libraries on Hackage. - Use Nix for installing Termonad. My suggestion is to use Nix, since it is highly likely to "just work" (because with Nix, _all_ libraries are pinned to known working versions, even system libraries). ### Ubuntu / Debian Termonad can be installed through `apt` on Debian and Ubuntu: ```console $ sudo apt install termonad libghc-termonad-dev ``` Note that the `libghc-termonad-dev` package is necessary if you want to be able to compile the Haskell-based settings file, `termonad.hs`. #### Compiling from source on Ubuntu / Debian First, you must install the required Gnome/GTK system libraries: ```sh $ apt-get install gobject-introspection libgirepository1.0-dev libgtk-3-dev libvte-2.91-dev libpcre2-dev ``` In order to install Termonad, clone this repository and run `stack install`. This will install the `termonad` binary to `~/.local/bin/`: ```sh $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ stack install ``` ### Nix If you have `nix` installed, you should be able to use it to obtain Termonad. This means that it will work on NixOS, or with `nix` on another distro. There are three different ways to use `nix` to get Termonad: 1. Get Termonad from Nixpkgs. Termonad is provided as a top-level `termonad` attribute in Nixpkgs. For instance, run a `nix-shell` with Termonad: ```console $ nix-shell -p termonad $ termonad # run termonad within the nix-shell ``` You can also install `termonad` with tools like `nix-env` or home-manager. If you're using NixOS, you can add `termonad` to your `environment.systemPackages` list. Keep in mind that if you're using an old release of NixOS, you'll likely get an older version of Termonad. 2. Build Termonad using the code in this repository. The following commands clone this repo and build the `termonad` binary at `./result/bin/`: ```sh $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ nix-build ``` 3. Build Termonad using `stack` with Nix-integration. The following commands install `stack` for your user, clone this repository, and install the `termonad` binary to `~/.local/bin/`: ```sh $ nix-env -i stack $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ stack --nix install ``` (_edit_: Building with `stack` using Nix-integration does not currently work. See [#99](https://github.com/cdepillabout/termonad/issues/99).) ### Mac OS X Building and installing Termonad on Mac OS X should be possible with any of the following three methods: - Install the required system libraries (like GTK and VTE) by hand, then use `stack` to build Termonad. This is probably the easiest method. You don't have to understand anything about `nix`. However, it is slightly annoying to have to install GTK and VTE by hand. - Use `nix` to install both the required system libraries and Termonad itself. If you are a nix user and want an easy way to install Termonad, this is the recommended method. - Use `nix` to install install the required system libraries, and `stack` to build Termonad. If you are a nix user, but want to use `stack` to actually do development on Termonad, using `stack` may be easier than using `cabal`. The following sections describe each method. #### Installing with just `stack` (*currently no instructions available. please send a PR adding instructions if you get termonad to build using this method.*) #### Installing with just `nix` `nix` can be used to install Termonad with the following steps, assuming you have `nix` [installed](https://nixos.org/nix/download.html). These commands clone this repository and build the `termonad` binary at `./result/bin/`: ```sh $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ nix-build ``` #### Installing with `stack` using `nix` `stack` can be used in conjunction with `nix` to install Termonad. `nix` will handle installing system dependencies (like GTK and VTE), while `stack` will handle compiling and installing Haskell packages. You must have `nix` [installed](https://nixos.org/nix/download.html). You will also need `stack` installed. You can do that with the following command: ```sh $ nix-env -i stack ``` After `stack` is installed, you will need to clone Termonad and build it: ``` $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ stack --nix install ``` This will install the `termonad` binary to `~/.local/bin/`. ### Windows To run Termonad on Windows, you'll need: * any X server app, for example **Vcxsrv** * any WSL, for example **Ubuntu** I'm using both Vcxsrv and Ubuntu WSL. Configure both Vcxsrv and WSL. For Vcxsrv go with default settings everywhere, it will be fine. Configure your WSL as you want (choose your name etc.). After you set up the user, you'll have to update your OS, run: ```console $ sudo apt-get update $ sudo apt-get upgrade -y $ sudo apt-get dist-upgrade -y $ sudo apt-get autoremove -y ``` Configure the `DISPLAY` environment variable for the X server, and load the changes in bash: For WSL1: ```console $ echo "export DISPLAY=localhost:0.0" >> ~/.bashrc $ source ~/.bashrc ``` For WSL2: ```console $ echo export DISPLAY=$(awk '/nameserver / {print $2; exit}' /etc/resolv.conf 2>/dev/null):0 >> ~/.bashrc $ echo export LIBGL_ALWAYS_INDIRECT=1 >> ~/.bashrc $ source ~/.bashrc ``` If you're using WSL2, you have to create a separate **inbound rule** for TCP port 6000, to allow WSL access to the X server. If you're using mentioned earlier **Vcxsrv** you can enable public access for your X server by disabling Access Control on the Extra Settings. You can also use `-ac` flag in the Additional parameters for VcXsrv section. Your X server should now be configured. Execute following command to install the necessary GTK system libraries: ```console $ apt-get install gobject-introspection libgirepository1.0-dev libgtk-3-dev libvte-2.91-dev libpcre2-dev ``` The required GTK system libraries should now be installed. Clone the Termonad repo: ```sh $ git clone https://github.com/cdepillabout/termonad $ cd termonad/ $ stack build $ stack run ``` After `stack run`, you should see a new window with your Termonad running. ## How to use Termonad Termonad is similar to XMonad. The above steps will install a `termonad` binary somewhere on your system. If you have installed Termonad using `stack`, the `termonad` binary will be in `~/.local/bin/`. This binary is a version of Termonad configured with default settings. You can try running it to get an idea of what Termonad is like: ```sh $ ~/.local/bin/termonad ``` The following section describes the default key bindings. If you would like to configure Termonad with your own settings, first you will need to create a Haskell file called `~/.config/termonad/termonad.hs`. A following section gives an example configuration file. If this configuration file exists, when the `~/.local/bin/termonad` binary launches, it will try to use GHC to compile the configuration file. If GHC is able to successfully compile the configuration file, a separate binary will be created called something like `~/.cache/termonad/termonad-linux-x86_64`. This binary file can be thought of as your own personal Termonad, configured with all your own settings. When you run `~/.local/bin/termonad`, it will re-exec `~/.cache/termonad/termonad-linux-x86_64` if it exists. However, there is one difficulty with this setup. In order for the `~/.local/bin/termonad` binary to be able to compile your `~/.config/termonad/termonad.hs` configuration file, Termonad needs to know where GHC is, as well as where all your Haskell packages live. This presents some difficulties that will be discussed in a following section. ### Default Key Bindings Termonad provides the following default key bindings. | Key binding | Action | |------------|--------| | Ctrl Shift t | Open new tab. | | Ctrl Shift w | Close tab. | | Ctrl Shift f | Open Find dialog for searching for a regex. | | Ctrl Shift p | Find the regex **above** the current position. | | Ctrl Shift i | Find the regex **below** the current position. | | Ctrl + | Increase font size. | | Ctrl - | Decrease font size. | | Ctrl PgUp | Switch to previous tab. | | Ctrl PgDown | Switch to next tab. | | Alt (number key) | Switch to tab `number`. For example, Alt 2 switches to tab 2. | ### Configuring Termonad Termonad has two different ways to be configured. The first way is to use the built-in Preferences editor. You can find this in the `Preferences` menu under `Edit` in the menubar. When opening Termonad for the first time, it will create a preferences file at `~/.config/termonad/termonad.yaml`. When you change a setting in the Preferences editor, Termonad will update the setting in the preferences file. When running Termonad, it will load settings from the preferences file. Do not edit the preferences file by hand, because it will be overwritten when updating settings in the Preferences editor. This method is perfect for users who only want to make small changes to the Termonad settings, like the default font size. The second way to configure Termonad is to use a Haskell-based settings file, called `~/.config/termonad/termonad.hs` by default. This method allows you to make large, sweeping changes to Termonad. This method is recommended for power users. **WARNING: If you have a `~/.config/termonad/termonad.hs` file, then all settings from `~/.config/termonad/termonad.yaml` will be ignored. If you want to set *ANY* settings in `~/.config/termonad/termonad.hs`, then you must set *ALL* settings in `~/.config/termonad/termonad.hs`.** The following is an example Termonad configuration file. You should save this to `~/.config/termonad/termonad.hs`. You can find more information on the available configuration options within the [`Termonad.Config`](https://hackage.haskell.org/package/termonad/docs/Termonad-Config.html) module. ```haskell {-# LANGUAGE OverloadedStrings #-} module Main where import Termonad.App (defaultMain) import Termonad.Config ( FontConfig, FontSize(FontSizePoints), Option(Set) , ShowScrollbar(ShowScrollbarAlways), defaultConfigOptions, defaultFontConfig , defaultTMConfig, fontConfig, fontFamily, fontSize, options, showScrollbar ) import Termonad.Config.Colour ( AlphaColour, ColourConfig, addColourExtension, createColour , createColourExtension, cursorBgColour, defaultColourConfig ) -- | This sets the color of the cursor in the terminal. -- -- This uses the "Data.Colour" module to define a dark-red color. -- There are many default colors defined in "Data.Colour.Names". cursBgColour :: AlphaColour Double cursBgColour = createColour 204 0 0 -- | This sets the colors used for the terminal. We only specify the background -- color of the cursor. colConf :: ColourConfig (AlphaColour Double) colConf = defaultColourConfig { cursorBgColour = Set cursBgColour } -- | This defines the font for the terminal. fontConf :: FontConfig fontConf = defaultFontConfig { fontFamily = "DejaVu Sans Mono" , fontSize = FontSizePoints 13 } main :: IO () main = do colExt <- createColourExtension colConf let termonadConf = defaultTMConfig { options = defaultConfigOptions { fontConfig = fontConf -- Make sure the scrollbar is always visible. , showScrollbar = ShowScrollbarAlways } } `addColourExtension` colExt defaultMain termonadConf ``` There are other example configuration files in the [example-config/](./example-config) directory. If you want to test what all the colors look like, you may find it convenient to use the [`print-console-colors`](http://hackage.haskell.org/package/print-console-colors) package, which provides an executable called `print-console-colors` that prints all of the colors for your terminal. ### Compiling Local Settings If you launch Termonad by calling `~/.local/bin/termonad`, it will try to compile the `~/.config/termonad/termonad.hs` file if it exists. The problem is that `~/.local/bin/termonad` needs to be able to see GHC and the required Haskell libraries to be able to compile `~/.config/termonad/termonad.hs`. There are a couple solutions to this problem, listed in the sections below. (These steps are definitely confusing. I would love to figure out a better way to do this. Please submit an issue or PR if you have a good idea about how to fix this.) #### Running with `stack` If you originally compiled Termonad with `stack`, you can use `stack` to execute Termonad. First, you must change to the directory with the Termonad source code. From there, you can run `stack exec`: ```sh $ cd termonad/ # change to the termonad source code directory $ stack exec -- termonad ``` `stack` will pick up the correct GHC version and libraries from the `stack.yaml` and `termonad.cabal` file. `termonad` will be run in an environment with GHC available. `termonad` will use this GHC and libraries to compile your `~/.config/termonad/termonad.hs` file. It if succeeds, it should create a `~/.cache/termonad/termonad-linux-x86_64` binary. If you need extra Haskell libraries available when compiling your `~/.config/termonad/termonad.hs` file, you can specify them to `stack exec`: ```sh $ stack exec --package lens --package conduit -- termonad ``` The problem with this is that `stack exec` changes quite a few of your environment variables. It is not recommended to actually run Termonad from within `stack exec`. After you run `stack exec -- termonad` and let it recompile your `~/.config/termonad/termonad.hs` file, exit Termonad. Re-run Termonad by calling it directly. Termonad will notice that `~/.config/termonad/termonad.hs` hasn't changed since `~/.cache/termonad/termonad-linux-x86_64` has been recompiled, so it will directly execute `~/.cache/termonad/termonad-linux-x86_64`. #### Running with `nix` Building Termonad with `nix` (by running `nix-build` in the top directory) sets it up so that Termonad can see GHC. Termonad should be able to compile the `~/.config/termonad/termonad.hs` file by default. If you're interested in how this works, or want to change which Haskell packages are available from your `~/.config/termonad/termonad.hs` file, please see the documentation in the [`.nix-helpers/termonad-with-packages.nix`](./.nix-helpers/termonad-with-packages.nix) file. ## Additional Info This section contains some additional info that may be helpful for using Termonad. ### Opening URLs by right-clicking It is possible to open a URL in a browser by right-clicking on it, and selecting `Open URL in browser`. In order for this you work, you may have to setup your XDG defaults. You can set the default browser to Firefox with a command like the following: ```console $ xdg-mime default firefox.desktop x-scheme-handler/http $ xdg-mime default firefox.desktop x-scheme-handler/https ``` This `xdg-mime` executable comes from a package called `xdg-utils` in both Nixpkgs and Ubutun/Debian. ## Goals Termonad has the following goals: * fully configurable in Haskell There are already [many](https://gnometerminator.blogspot.com/p/introduction.html) [good](https://www.enlightenment.org/about-terminology.md) [terminal](http://software.schmorp.de/pkg/rxvt-unicode.html) [emulators](https://launchpad.net/sakura). However, there are no terminal emulators fully configurable in Haskell. Termonad fills this niche. * flexible Most people only need a terminal emulator that lets you change the font-size, cursor color, etc. They don't need tons of configuration options. Termonad should be for people that like lots of configuration options. Termonad should provide many hooks to allow the user full control over its behavior. * stable Termonad should be able to be used everyday as your main terminal emulator. It should not crash for any reason. If you experience a crash, please file an issue or a pull request! * good documentation The [documentation](https://hackage.haskell.org/package/termonad) for Termonad on Hackage should be good. You shouldn't have to guess at what certain data types or functions do. If you have a hard time understanding anything in the documentation, please submit an issue or PR. ## Where to get help If you find a bug in Termonad, please either [send a PR](https://github.com/cdepillabout/termonad/pulls) fixing it or create an [issue](https://github.com/cdepillabout/termonad/issues) explaining it. If you just need help with configuring Termonad, you can either join the [Gitter room](https://gitter.im/termonad/Lobby) or [#termonad on irc.freenode.net](https://webchat.freenode.net/). ## Contributions Contributions are highly appreciated. Termonad is currently missing many helpful configuration options and behavior hooks. If there is something you would like to add, please submit an issue or PR. ## Maintainers - [cdepillabout](https://github.com/cdepillabout)