diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 01502b1..7c180bb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,36 +1,77 @@
-# This is a basic workflow to help you get started with Actions
+---
+#
+# Copyright (C) 2024 James Cherti
+# URL: https://github.com/jamescherti/easysession.el
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This file is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs. If not, see .
+#
name: CI
+on: [push, pull_request]
-# Controls when the workflow will run
-on:
- # Triggers the workflow on push or pull request events but only for the "main" branch
- push:
- branches: [ "main" ]
- pull_request:
- branches: [ "main" ]
-
- # Allows you to run this workflow manually from the Actions tab
- workflow_dispatch:
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
- # This workflow contains a single job called "build"
- build:
- # The type of runner that the job will run on
+ test:
runs-on: ubuntu-latest
-
- # Steps represent a sequence of tasks that will be executed as part of the job
+ strategy:
+ matrix:
+ emacs-version:
+ - 26.3
+ - 27.1
+ - 28.2
+ - 29.1
+ - 29.4
+ python-version:
+ - 3.11
steps:
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v1
+ with:
+ python-version: ${{ matrix.python-version }}
+ - uses: purcell/setup-emacs@master
+ with:
+ version: ${{ matrix.emacs-version }}
+ - uses: actions/cache@v2
+ id: cache-cask-packages
+ with:
+ path: .cask
+ key: cache-cask-packages-000
+ - uses: actions/cache@v2
+ id: cache-cask-executable
+ with:
+ path: ~/.cask
+ key: cache-cask-executable-000
+ - uses: cask/setup-cask@master
+ if: steps.cache-cask-executable.outputs.cache-hit != 'true'
+ with:
+ version: snapshot
+ - run: echo "$HOME/.cask/bin" >> $GITHUB_PATH
- # Runs a single command using the runners shell
- - name: Run a one-line script
- run: echo Hello, world!
-
- # Runs a set of commands using the runners shell
- - name: Run a multi-line script
+ - name: Install
run: |
- echo Add other actions to build,
- echo test, and deploy your project.
+ python -m pip install --upgrade pip
+ sudo apt-get install emacs && emacs --version
+ git clone https://github.com/riscy/melpazoid.git ~/melpazoid
+ pip install ~/melpazoid
+
+ - name: Run melpazoid
+ env:
+ RECIPE: (easysession :repo "jamescherti/easysession.el" :branch "${{ github.ref_name }}" :fetcher github :files ("*.el"))
+ EXIST_OK: false
+ run: echo $GITHUB_REF && make -C ~/melpazoid
+
+ - name: Run tests
+ run: make test
+ env:
+ CASK_PATH: $HOME/.cask/bin
diff --git a/Cask b/Cask
new file mode 100644
index 0000000..1969ca7
--- /dev/null
+++ b/Cask
@@ -0,0 +1,4 @@
+(source gnu)
+(source melpa)
+
+(package-file "easysession.el")
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0ddeb51
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,38 @@
+#
+# Copyright (C) 2024 James Cherti
+# URL: https://github.com/jamescherti/easysession.el
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This file is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs. If not, see .
+#
+
+export EMACS ?= $(shell command -v emacs 2>/dev/null)
+CASK_DIR := $(shell cask package-directory)
+
+$(CASK_DIR): Cask
+ cask install
+ @touch $(CASK_DIR)
+
+.PHONY: cask
+cask: $(CASK_DIR)
+
+.PHONY: compile
+compile: cask
+ cask emacs -batch -L . -L test \
+ --eval "(setq byte-compile-error-on-warn t)" \
+ -f batch-byte-compile $$(cask files); \
+ (ret=$$? ; cask clean-elc && exit $$ret)
+
+.PHONY: test
+test: compile
+ cask emacs --batch -L . -L tests -l tests/test-easysession.el -f test-easysession
diff --git a/README.md b/README.md
index c658286..7929d13 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
# easysession.el - Easily persist and restore your Emacs editing sessions
[![MELPA](https://melpa.org/packages/easysession-badge.svg)](https://melpa.org/#/easysession)
![](https://raw.githubusercontent.com/jamescherti/easysession.el/main/.images/made-for-gnu-emacs.svg)
+![Build Status](https://github.com/jamescherti/easysession.el/actions/workflows/ci.yml/badge.svg)
![License](https://img.shields.io/github/license/jamescherti/easysession.el)
The `easysession.el` Emacs package is a lightweight session manager for Emacs that can persist and restore file editing buffers, indirect buffers/clones, Dired buffers, windows/splits, the built-in tab-bar (including tabs, their buffers, and windows), and Emacs frames. It offers a convenient and effortless way to manage Emacs editing sessions and utilizes built-in Emacs functions to persist and restore frames.
diff --git a/easysession.el b/easysession.el
index b90e47c..78f340d 100644
--- a/easysession.el
+++ b/easysession.el
@@ -350,7 +350,7 @@ Raise an error if the session name is invalid."
(error "[easysession] Invalid session name: %s" session-name))
session-name)
-(defun easysession-set-current-session (&optional session-name)
+(defun easysession--set-current-session (&optional session-name)
"Set the current session to SESSION-NAME.
Return t if the session name is successfully set."
(easysession--ensure-session-name-valid session-name)
@@ -716,8 +716,9 @@ SESSION-NAME is the name of the session."
'utf-8 session-file)
t)))
(if fwrite-success
- (when (called-interactively-p 'any)
- (easysession--message "Session saved: %s" session-name)
+ (progn
+ (when (called-interactively-p 'any)
+ (easysession--message "Session saved: %s" session-name))
(run-hooks 'easysession-after-save-hook))
(error "[easysession] %s: failed to save the session to %s"
session-name session-file)))
@@ -798,7 +799,7 @@ If the function is called interactively, ask the user."
(previous-session-name easysession--current-session-name))
(easysession--ensure-session-name-valid new-session-name)
(easysession-save new-session-name)
- (easysession-set-current-session new-session-name)
+ (easysession--set-current-session new-session-name)
(if (string= previous-session-name easysession--current-session-name)
(easysession--message "Saved the session: %s" new-session-name)
(easysession--message "Saved and switched to session: %s"
@@ -841,7 +842,7 @@ initialized."
(easysession-load session-name)
(unless session-reloaded
- (easysession-set-current-session session-name)
+ (easysession--set-current-session session-name)
(unless (file-exists-p session-file)
(run-hooks 'easysession-new-session-hook)
(easysession-save)
diff --git a/tests/test-easysession.el b/tests/test-easysession.el
new file mode 100644
index 0000000..a0f31e5
--- /dev/null
+++ b/tests/test-easysession.el
@@ -0,0 +1,276 @@
+;;; test-easysession.el --- Easysession tests -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 James Cherti | https://www.jamescherti.com/contact/
+
+;; Author: James Cherti
+;; URL: https://github.com/jamescherti/easysession.el
+;; Keywords: convenience
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see .
+
+;;; Commentary:
+;; Test the easysession package.
+
+;;; Code:
+
+(require 'dired)
+(require 'easysession)
+
+(defvar test-easysession--before-load-hook-triggered nil
+ "Flag indicating whether `easysession-before-load-hook' has been executed.")
+
+(defvar test-easysession--after-load-hook-triggered nil
+ "Flag indicating whether `easysession-after-load-hook' has been executed.")
+
+(defvar test-easysession--before-save-hook-triggered nil
+ "Flag indicating whether `easysession-before-save-hook' has been executed.")
+
+(defvar test-easysession--after-save-hook-triggered nil
+ "Flag indicating whether `easysession-after-save-hook' has been executed.")
+
+(defvar test-easysession--new-session-hook-triggered nil
+ "Flag indicating whether `easysession-new-session-hook' has been executed.")
+
+(defvar test-easysession--file-buffer1-path "~/buffer1"
+ "Path to the first test file editing buffer.")
+
+(defvar test-easysession--file-buffer2-path "~/buffer2"
+ "Path to the second test file editing buffer.")
+
+(defvar test-easysession--dired-buffer-path "~/"
+ "Path to the directory for the Dired buffer.")
+
+(defvar test-easysession--dired-buffer nil
+ "Reference to the Dired buffer.")
+
+(defvar test-easysession--file-buffer1 nil
+ "Reference to the first test buffer.")
+
+(defvar test-easysession--file-buffer2 nil
+ "Reference to the second test buffer.")
+
+(defvar test-easysession--indirect-buffer1-name "indirect-buffer1"
+ "Name of the indirect test buffer.")
+
+(defvar test-easysession--indirect-buffer1 nil
+ "Reference to the indirect test buffer.")
+
+(defun test-easysession--add-hooks ()
+ "Add and configure hooks for testing `easysession`.
+Tracks the execution of session-related hooks and performs checks
+to ensure expected buffer states before and after loading or saving."
+ (interactive)
+ (when test-easysession--new-session-hook-triggered
+ (error (concat "The `easysession-new-session-hook` should not be "
+ "triggered before the switch to another session.")))
+
+ (add-hook 'easysession-new-session-hook
+ #'(lambda () (setq test-easysession--new-session-hook-triggered t)))
+
+ (add-hook 'easysession-before-load-hook
+ #'(lambda ()
+ (when (get-file-buffer test-easysession--file-buffer1-path)
+ (error "Before-load: Buffer 1 should not be open"))
+ (setq test-easysession--before-load-hook-triggered t)))
+
+ (add-hook 'easysession-after-load-hook
+ #'(lambda ()
+ (setq test-easysession--after-load-hook-triggered t)))
+
+ (add-hook 'easysession-before-save-hook
+ #'(lambda ()
+ (setq test-easysession--before-save-hook-triggered t)))
+
+ (add-hook 'easysession-after-save-hook
+ #'(lambda ()
+ (setq test-easysession--after-save-hook-triggered t))))
+
+(defun test-easysession--switch-session ()
+ "Test the `easysession-switch-to' function.
+Test the `easysession-switch-to' function by switching to a test session. Checks
+if the `easysession-new-session-hook' is correctly executed and verifies the
+session name before and after the switch."
+ (interactive)
+ ;; Verify the initial session name
+ (unless (string= "main" (easysession-get-session-name))
+ (error ("Expected the initial session to be named 'main', but found '%s'"
+ (easysession-get-session-name))))
+
+ ;; Switch to the test session
+ (easysession-save)
+ (easysession-switch-to "test")
+
+ ;; Verify the session name after switching
+ (unless (string= "test" (easysession-get-session-name))
+ (error "Expected the session to be named 'test', but found '%s'"
+ (easysession-get-session-name)))
+
+ ;; Check if the new session hook was executed
+ (unless test-easysession--new-session-hook-triggered
+ (error (concat "The `easysession-new-session-hook` was not triggered "
+ "after switching sessions."))))
+
+(defun test-easysession--add-remove-handlers ()
+ "Test adding and removing easysession save and load handlers.
+This function ensures that handlers are correctly removed and re-added, and
+validates the handler lists after each operation."
+ (interactive)
+ ;; Remove existing save and load handlers
+ (easysession-remove-save-handler 'easysession--handler-save-file-editing-buffers)
+ (easysession-remove-save-handler 'easysession--handler-save-indirect-buffers)
+ (easysession-remove-load-handler 'easysession--handler-load-file-editing-buffers)
+ (easysession-remove-load-handler 'easysession--handler-load-indirect-buffers)
+
+ ;; Validate that handler lists are empty after removal
+ (unless (null easysession--load-handlers)
+ (error "Load handlers list is not empty after removal"))
+ (unless (null easysession--save-handlers)
+ (error "Save handlers list is not empty after removal"))
+
+ ;; Re-add save and load handlers
+ (easysession-add-save-handler 'easysession--handler-save-file-editing-buffers)
+ (easysession-add-save-handler 'easysession--handler-save-indirect-buffers)
+ (easysession-add-load-handler 'easysession--handler-load-file-editing-buffers)
+ (easysession-add-load-handler 'easysession--handler-load-indirect-buffers)
+
+ ;; Validate that handlers were correctly added
+ (unless (equal easysession--load-handlers
+ '(easysession--handler-load-file-editing-buffers
+ easysession--handler-load-indirect-buffers))
+ (error "Load handlers were not added correctly"))
+ (unless (equal easysession--save-handlers
+ '(easysession--handler-save-file-editing-buffers
+ easysession--handler-save-indirect-buffers))
+ (error "Save handlers were not added correctly")))
+
+(defun test-easysession--create-buffers ()
+ "Create and set up test buffers for easysession.
+This function creates file buffers, a Dired buffer, and an indirect buffer,
+storing them in respective variables for later use."
+ (interactive)
+ ;; File editing buffers
+ (with-temp-buffer
+ (insert "hello world")
+ (write-file test-easysession--file-buffer1-path))
+
+ (with-temp-buffer
+ (insert "hello world2")
+ (write-file test-easysession--file-buffer2-path))
+
+ (setq test-easysession--file-buffer1
+ (find-file-noselect test-easysession--file-buffer1-path))
+ (unless test-easysession--file-buffer1
+ (error "Failed to create test-easysession--file-buffer1"))
+
+ (setq test-easysession--file-buffer2
+ (find-file-noselect test-easysession--file-buffer2-path))
+ (unless test-easysession--file-buffer2
+ (error "Failed to create test-easysession--file-buffer2"))
+
+ ;; Dired buffer
+ (setq test-easysession--dired-buffer
+ (dired-noselect test-easysession--dired-buffer-path))
+ (unless test-easysession--dired-buffer
+ (error "Failed to create test-easysession--dired-buffer"))
+
+ ;; Indirect buffer
+ (with-current-buffer test-easysession--file-buffer1
+ (clone-indirect-buffer test-easysession--indirect-buffer1-name nil))
+ (setq test-easysession--indirect-buffer1
+ (get-buffer test-easysession--indirect-buffer1-name))
+ (unless test-easysession--indirect-buffer1
+ (error "Failed to create test-easysession--indirect-buffer1")))
+
+(defun test-easysession--save-load ()
+ "Test persisting and restoring: file editing buffers and indirect-buffer."
+ (interactive)
+ (unless (get-file-buffer test-easysession--file-buffer1-path)
+ (error "Before-save: Buffer 1 should be open"))
+
+ ;; Save session and kill buffers
+ (easysession-save)
+ (unless test-easysession--after-save-hook-triggered
+ (error "The easysession-after-save-hook was not triggered"))
+ (unless test-easysession--before-save-hook-triggered
+ (error "The easysession-before-save-hook was not triggered"))
+ (kill-buffer test-easysession--file-buffer1)
+ (when (get-file-buffer test-easysession--file-buffer1-path)
+ (error "The second buffer is still open"))
+
+ (kill-buffer test-easysession--file-buffer2)
+ (when (get-file-buffer test-easysession--file-buffer2-path)
+ (error "The second buffer is still open"))
+
+ (kill-buffer test-easysession--dired-buffer)
+ (when (buffer-live-p test-easysession--dired-buffer)
+ (error "The Dired buffer is still open"))
+
+ (kill-buffer test-easysession--indirect-buffer1)
+ (when (get-buffer test-easysession--indirect-buffer1-name)
+ (error "The indirect buffer is still open"))
+
+ ;; Load session
+ (easysession-load)
+ (unless test-easysession--after-load-hook-triggered
+ (error "The easysession-after-load-hook was not triggered"))
+ (unless test-easysession--before-load-hook-triggered
+ (error "The easysession-before-load-hook was not triggered"))
+
+ (setq test-easysession--file-buffer1
+ (get-file-buffer test-easysession--file-buffer1-path))
+ (when (not test-easysession--file-buffer1)
+ (error "Failed to first buffer"))
+
+ (setq test-easysession--file-buffer2
+ (get-file-buffer test-easysession--file-buffer2-path))
+ (when (not test-easysession--file-buffer2)
+ (error "Failed to the second buffer"))
+
+ (setq test-easysession--indirect-buffer1
+ (get-buffer test-easysession--indirect-buffer1-name))
+ (when (not test-easysession--indirect-buffer1)
+ (error
+ "Failed to restore the indirect buffer"))
+
+ (setq test-easysession--dired-buffer
+ (dired-noselect test-easysession--dired-buffer-path))
+ (when (not test-easysession--dired-buffer)
+ (error
+ "Failed to restore the Dired buffer"))
+ (with-current-buffer test-easysession--dired-buffer
+ (unless (string= (expand-file-name test-easysession--dired-buffer-path)
+ (expand-file-name default-directory))
+ (error
+ "The Dired buffer points to the wrong path"))))
+
+(defun test-easysession--get-all-names ()
+ "Test: `easysession--get-all-names'."
+ (interactive)
+ (unless (equal (easysession--get-all-names) '("main" "test"))
+ (error "The easysession--get-all-names failed")))
+
+(defun test-easysession ()
+ "Test easysession."
+ (interactive)
+ (test-easysession--add-hooks)
+ (test-easysession--switch-session)
+ (test-easysession--add-remove-handlers)
+ (test-easysession--create-buffers)
+ (test-easysession--save-load)
+ (test-easysession--get-all-names)
+ (message "Success: test-easysession"))
+
+(provide 'test-easysession)
+;;; test-easysession.el ends here