From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Philip Kaludercic Newsgroups: gmane.emacs.devel Subject: Re: Propose to add setup-wizard.el to ELPA Date: Sun, 02 Jan 2022 12:02:11 +0000 Message-ID: <87zgoeqrb0.fsf@posteo.net> References: <897ED591-43BC-4029-912A-917E5E9F6930@gmail.com> Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="26839"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Emacs developers To: Yuan Fu Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sun Jan 02 13:04:11 2022 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1n3zap-0006fu-UC for ged-emacs-devel@m.gmane-mx.org; Sun, 02 Jan 2022 13:04:08 +0100 Original-Received: from localhost ([::1]:39232 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1n3zao-0006LJ-3D for ged-emacs-devel@m.gmane-mx.org; Sun, 02 Jan 2022 07:04:06 -0500 Original-Received: from eggs.gnu.org ([209.51.188.92]:46104) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1n3zZ4-00045n-3W for emacs-devel@gnu.org; Sun, 02 Jan 2022 07:02:18 -0500 Original-Received: from mout01.posteo.de ([185.67.36.65]:56561) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1n3zZ0-00035O-LX for emacs-devel@gnu.org; Sun, 02 Jan 2022 07:02:17 -0500 Original-Received: from submission (posteo.de [89.146.220.130]) by mout01.posteo.de (Postfix) with ESMTPS id CF27E240026 for ; Sun, 2 Jan 2022 13:02:12 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1641124932; bh=/IEgnDKtm7cBXpKzqf3txKUGFarhBOiRnisa+2uj2M0=; h=From:To:Cc:Subject:Autocrypt:Date:From; b=bo4AWN9WoVRyULVsVRfLX14fIBf5pm+wIw89IEY/EUODylcSPVynGGYHZ9ZhTiOB4 kEXHw4U2IN2Zymu8n2oSSDullAGIK0q1wh9q8L+9iPuNqFjkFmDIGY4pViq5NLSHAF 4bMbfmLf9Ydc6g6OBS2HomEjdmV1MKmHdvip7SDOtn8gCHXVXQSc4PScNCKT9hRji7 h6LJfNfFzp0aAN+swh8y/SSBL5o++3iEOB5PsFAEHllB2S6WB3YqpW2pR1QBERVhbb u2vpjAGbpmU3JHwYkZJybVz7yvepZEymW6IT9Y33yDglAhGJA+vfxTc4l9WnFUGrha WXdx2jfhoX5zQ== Original-Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4JRctr2NdYz9rxL; Sun, 2 Jan 2022 13:02:12 +0100 (CET) Autocrypt: addr=philipk@posteo.net; prefer-encrypt=nopreference; keydata= mDMEYHHqUhYJKwYBBAHaRw8BAQdAp3GdmYJ6tm5McweY6dEvIYIiry+Oz9rU4MH6NHWK0Ee0QlBo aWxpcCBLYWx1ZGVyY2ljIChnZW5lcmF0ZWQgYnkgYXV0b2NyeXB0LmVsKSA8cGhpbGlwa0Bwb3N0 ZW8ubmV0PoiQBBMWCAA4FiEEDM2H44ZoPt9Ms0eHtVrAHPRh1FwFAmBx6lICGwMFCwkIBwIGFQoJ CAsCBBYCAwECHgECF4AACgkQtVrAHPRh1FyTkgEAjlbGPxFchvMbxzAES3r8QLuZgCxeAXunM9gh io0ePtUBALVhh9G6wIoZhl0gUCbQpoN/UJHI08Gm1qDob5zDxnIHuDgEYHHqUhIKKwYBBAGXVQEF AQEHQNcRB+MUimTMqoxxMMUERpOR+Q4b1KgncDZkhrO2ql1tAwEIB4h4BBgWCAAgFiEEDM2H44Zo Pt9Ms0eHtVrAHPRh1FwFAmBx6lICGwwACgkQtVrAHPRh1Fw1JwD/Qo7kvtib8jy7puyWrSv0MeTS g8qIxgoRWJE/KKdkCLEA/jb9b9/g8nnX+UcwHf/4VfKsjExlnND3FrBviXUW6NcB In-Reply-To: <897ED591-43BC-4029-912A-917E5E9F6930@gmail.com> (Yuan Fu's message of "Sat, 1 Jan 2022 18:07:53 -0800") Received-SPF: pass client-ip=185.67.36.65; envelope-from=philipk@posteo.net; helo=mout01.posteo.de X-Spam_score_int: -24 X-Spam_score: -2.5 X-Spam_bar: -- X-Spam_report: (-2.5 / 5.0 requ) DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:283894 Archived-At: I am not sure if I brought this up last time, but how difficult do you think it would be to generalise this into a "wizard.el" package, that could be used by any package to define an interactive configuration wizard? Yuan Fu writes: > A while ago I wrote a package that helps a new user to configure > Emacs: it takes a user through some interactive pages, where changes > takes effect immediately; and in the end it generates some code that > can be copied to init.el. > > Demo for the original package: https://youtu.be/0qMskTAR2aw > > I made some improvements to that package and renamed it > setup-wizard. Do you think we could add it to ELPA? Maybe the name is > too =E2=80=9Cofficial=E2=80=9D, in that case I can rename it to yuan=E2= =80=99s-setup-wizard or > something. > > I don=E2=80=99t know how useful could it be, since nowadays every body > (understandably) starts with some community distribution rather than > vanilla Emacs, but surely it is better than not having a wizard. > > You can try it out with emacs -q -l setup-wizard.el -f setup-wizard > > Yuan > > ;;; setup-wizard.el --- Setup wizard -*- lexical-binding: t; -*- > > ;; Copyright (C) 2019-2020 Free Software Foundation, Inc. > > ;; Author: Yuan Fu > ;; Maintainer: Yuan Fu > ;; URL: https://github.com/casouri/setup-wizard > ;; Version: 1.0.0 > ;; Keywords: convenience > ;; Package-Requires: ((emacs "26.0")) > > ;; This file is part of GNU Emacs. > > ;; GNU Emacs 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 3 of the License, or > ;; (at your option) any later version. > > ;; GNU Emacs 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: > ;; > ;; This package provides a setup wizard that takes a user through an > ;; interactive interface, in which he or she can configure key > ;; bindings schemes, UI elements, Fonts, packages, etc. > > ;;; Code: > > (require 'widget) > (require 'wid-edit) > (require 'pcase) > (require 'seq) > (require 'cl-lib) > > ;;; Configs > > (defvar setup-wizard--config nil > "An alist (OPTION . (FORM COMMENT)) of configurations. > We use FORM and COMMENT to produce the final config.") > > (defun setup-wizard--save-option-and-eval > (option form comment &optional additional) > "Save OPTION FORM and COMMENT, and evaluate FORM. > If ADDITIONAL is non-nil, eval that too." > (when form > (setf (alist-get option setup-wizard--config) > (list form comment)) > (eval form)) > (when additional (eval additional))) > > ;;; Pages > > (defun setup-wizard--insert (&rest args) > "Insert ARGS and replace emojis if they can=E2=80=99t be displayed." > (widget-insert > (mapconcat (lambda (text) > (if (and (char-displayable-p ?=F0=9F=A7=99) > (char-displayable-p ?=F0=9F=A7=9A)) > text > (string-replace > "=F0=9F=A7=9A" "Fairy" > (string-replace > "=F0=9F=A7=99" "Wizard" text)))) > args))) > > ;;;; Themes > > (defvar setup-wizard--c-demo > " #include > > struct point > { > x: int; > y: int; > }; > > int main(int arg, int* argv) > { > int x =3D -1; > int y =3D 2; > void *buf =3D malloc(sizeof(uin32_t)); > return add(x, y) - 3; > } > " > "Demo C code.") > > (defun setup-wizard--theme-page () > "Theme configuration page." > (setup-wizard--insert > "=F0=9F=A7=9A: Heya! You are here for help setting up your Emacs, righ= t? > Wizard will be here when you read to the next line. > > =F0=9F=A7=99: Emacs comes with a couple of themes built-in, which are sho= wn > below. You can browse for more themes online or in the package > manager. > > =F0=9F=A7=9A: Here are the built-in themes! > > Theme preview:\n\n") > ;; Insert a C demo. > (widget-insert > (with-temp-buffer > (insert setup-wizard--c-demo) > (c-mode) > (font-lock-fontify-region (point-min) (point-max)) > (buffer-string))) > (widget-insert "\n") > ;; Insert theme selection menu. > (apply #'widget-create 'radio-button-choice > :follow-link t > :value "default" > ;; Enable the theme when the user selects it. > :notify (lambda (widget &rest _) > (mapc #'disable-theme custom-enabled-themes) > (let* ((theme (intern (widget-value widget))) > (form (if (eq theme 'default) > nil > `(load-theme ',theme)))) > (setup-wizard--save-option-and-eval > 'theme form (format "Load %s theme" theme)))) > (cons '(item "default") > (cl-loop for theme in (custom-available-themes) > collect `(item ,(symbol-name theme))))) > (setup-wizard--insert "\n=F0=9F=A7=9A: Want to ") > (widget-create > 'push-button > :notify (lambda (&rest _) > (package-refresh-contents) > (list-packages t) > (goto-char (point-min)) > (let ((inhibit-read-only t)) > (keep-lines "-theme"))) > :value "browse the package manager for themes") > (widget-insert "?\n")) > > ;;;; Keybinding > > (defun setup-wizard--keybinding-page () > "Keybinding page." > (setup-wizard--insert "=F0=9F=A7=99: This is the notation for modifiers= in Emacs: > > C (control) Ctrl > M (meta) Alt/Option > s (super) Windows/Command > S (shift) Shift > > =F0=9F=A7=9A: Which binding scheme do you like?\n\n") > (widget-create 'radio-button-choice > :follow-link t > :value "default" > :notify > (lambda (widget &rest _) > (setup-wizard--save-option-and-eval > 'keybinding > (cond > ((equal (widget-value widget) > "Alternative") > '(cua-mode)) > ((equal (widget-value widget) > "Wizard=E2=80=99s choice") > `(progn > (global-set-key (kbd "s-c") #'kill-ring-save) > (global-set-key (kbd "s-x") #'kill-region) > (global-set-key (kbd "s-v") #'yank)))) > "Set bindings for copy/cut/paste.")) > '(item :value "Default" > :format "%v\n\n%d\n" > :doc " M-w Copy > C-w Cut > C-y Paste") > '(item :value "Alternative" > :format "%v\n\n%d\n" > :doc " C-c Copy > C-x Cut > C-v Paste") > '(item :value "Wizard=E2=80=99s choice" > :format "%v\n\n%d\n" > :doc " s-c Copy > s-x Cut > s-v Paste")) > (setup-wizard--insert > "\n=F0=9F=A7=99: In the alternative binding scheme, the binding for co= py > and cut only take effect when some text is selected. So when > nothing is selected, they are still normal prefix keys.\n")) > > ;;;; UI features > > (defun setup-wizard--ui-features-page () > "UI features page." > (setup-wizard--insert "=F0=9F=A7=9A: What UI elements do you like?\n\n") > ;; Line numbers. > (widget-create 'checkbox > :notify > (lambda (widget &rest _) > (let ((val (widget-value widget))) > (setup-wizard--save-option-and-eval > 'line-number > `(global-display-line-numbers-mode > ,(if val 1 -1)) > (format "%s line number." > (if val "Display" "Don=E2=80=99t display"))= ))) > :value nil) > (widget-insert " Line numbers.\n") > ;; Thin cursor. > (widget-create 'checkbox > :notify > (lambda (widget &rest _) > (let ((val (widget-value widget))) > (setup-wizard--save-option-and-eval > 'thin-cursor > `(setq-default cursor-type > ',(if val 'bar t)) > (format "Use %s cursor" > (if val "thin" "default"))))) > :value nil) > (widget-insert " Thin cursor bar.\n") > ;; Blink cursor. > (widget-create 'checkbox > :notify > (lambda (widget &rest _) > (let ((val (widget-value widget))) > (setup-wizard--save-option-and-eval > 'blink-cursor > `(blink-cursor-mode ,(if val 1 -1)) > (format "%s cursor" > (if val "Blink" "Do not blink"))))) > :value blink-cursor-mode) > (widget-insert " Blink cursor.\n") > ;; Tool bar. > (widget-create 'checkbox > :notify > (lambda (widget &rest _) > (let ((val (widget-value widget))) > (setup-wizard--save-option-and-eval > 'tool-bar > `(tool-bar-mode ,(if val 1 -1)) > (format "%s tool bar." > (if val "Enable" "Disable"))))) > :value tool-bar-mode) > (widget-insert " Tool bar.\n") > ;; Menu bar. > (widget-create 'checkbox > :notify > (lambda (widget &rest _) > (let ((val (widget-value widget))) > (setup-wizard--save-option-and-eval > 'menu-bar > `(menu-bar-mode ,(if val 1 -1)) > (format "%s menu bar." > (if val "Enable" "Disable"))))) > :value menu-bar-mode) > (widget-insert " Menu bar.\n") > ;; Scroll bar. > (widget-create 'checkbox > :notify > (lambda (widget &rest _) > (let ((val (widget-value widget))) > (setup-wizard--save-option-and-eval > 'scroll-bar > `(scroll-bar-mode ,(if val 1 -1)) > (format "%s scroll bar" > (if val "Enable" "Disable"))))) > :value scroll-bar-mode) > (widget-insert " Scroll bar.\n") > ;; Font. > (widget-insert "\n") > (let* (default-font-field > variable-font-field > cjk-font-field > size-field > action > (phrase "The quick brown fox jumps over the lazy dog.\n")) > (widget-insert "Font preview:\n\n") > (widget-insert " " phrase) > (widget-insert " " (propertize phrase 'face 'variable-pitch)) > (widget-insert " =E5=A4=A7=E6=BC=A0=E5=AD=A4=E7=83=9F=E7=9B=B4=EF= =BC=8C=E9=95=BF=E6=B2=B3=E8=90=BD=E6=97=A5=E5=9C=86=E3=80=82\n") > (widget-insert " =E5=B0=84=E3=81=AF=E4=BB=81=E3=81=AE=E9=81=93=E3= =81=AA=E3=82=8A=E3=80=82=E5=B0=84=E3=81=AF=E6=AD=A3=E3=81=97=E3=81=8D=E3=82= =92=E5=B7=B1=E3=81=AB=E6=B1=82=E3=82=80=E3=80=82\n\n") > (setq default-font-field > (widget-create 'editable-field > :size 20 > :value "" > :format "Default font: %v \n")) > (setq variable-font-field > (widget-create 'editable-field > :size 20 > :value "" > :format "Variable-pitch font: %v \n")) > (setq cjk-font-field > (widget-create 'editable-field > :size 20 > :value "" > :format "CJK font: %v \n")) > (setq size-field > (widget-create 'editable-field > :size 2 > :value "" > :format "Font size: %v \n\n")) > (setq action > (lambda (&rest _) > (let* ((default-font > (string-trim (widget-value default-font-field))) > (variable-font > (string-trim (widget-value variable-font-field))) > (cjk-font > (string-trim (widget-value cjk-font-field))) > (size (string-to-number > (string-trim > (widget-value size-field))))) > (unless (equal default-font "") > (setup-wizard--save-option-and-eval > 'font `(set-face-attribute > 'default nil :family ,default-font) > "Set default font.")) > (unless (equal variable-font "") > (setup-wizard--save-option-and-eval > 'variable-font > `(set-face-attribute > 'variable-pitch nil :family ,variable-font) > "Set variable-pitch font.")) > (unless (equal cjk-font "") > (setup-wizard--save-option-and-eval > 'cjk-font > `(dolist (charset '(kana han cjk-misc)) > (set-fontset-font > t charset (font-spec :family ,cjk-font))) > "Set CJK font.")) > (unless (eq size 0) > (setup-wizard--save-option-and-eval > 'font-size > `(set-face-attribute 'default nil :height ,(* size 10)) > "Set font size."))))) > (widget-create 'push-button > :value "Apply font settings" > :notify action) > (widget-insert "\n"))) > > (defun setup-wizard--undo-page () > "Undo page." > (setup-wizard--insert > "=F0=9F=A7=99: Emacs has a powerful (but probably unintuitive) undo sy= stem, > where undo operations themselves are recorded in the undo > history, and redo is done by undoing an previous undo operation. > > =F0=9F=A7=9A: Which undo system do you like?\n\n") > (widget-create 'radio-button-choice > :value "default" > :follow-lint t > :notify (lambda (widget &rest _) > (when (equal (widget-value widget) > "Linear") > (setup-wizard--save-option-and-eval > 'undo > `(global-set-key [remap undo] #'undo-only) > "Use linear undo style."))) > '(item :value "Default" > :format "%v\n\n%d\n" > :doc " One undo rules them all") > '(item :value "Linear" > :format "%v\n\n%d\n" > :doc " Undo and redo")) > (let (undo-key redo-key) > (widget-insert "\n") > (setq undo-key > (widget-create 'editable-field > :size 5 > :value "C-/" > :format "Bind undo to: %v ")) > (widget-create 'push-button > :value "Apply" > :notify > (lambda (&rest _) > (let ((key (string-trim (widget-value undo-key)))) > (setup-wizard--save-option-and-eval > 'undo-key > `(global-set-key (kbd ,key) #'undo) > "Set binding for =E2=80=98undo=E2=80=99.")))) > (widget-insert "\n") > (setq redo-key > (widget-create 'editable-field > :size 5 > :value "C-?" > :format "Bind redo to: %v ")) > (widget-create 'push-button > :value "Apply" > :notify > (lambda (&rest _) > (let ((key (string-trim (widget-value redo-key)))) > (setup-wizard--save-option-and-eval > 'undo-key > `(global-set-key (kbd ,key) #'undo-redo) > "Set binding for =E2=80=98undo-redo=E2=80=99.")))) > (setup-wizard--insert "\n\n=F0=9F=A7=99: I bind redo to C-.\n"))) > > ;;;; Extra package > > (defun setup-wizard--package-activate (package mode) > "Return a form that activates PACKAGE and enable MODE." > `(progn > (require 'package) > (unless (package-installed-p ',package) > (package-install ',package)) > (package-activate 'ivy) > (require ',package) > (,mode))) > > (defun setup-wizard--package-page () > "Extra package page." > (setup-wizard--insert > "=F0=9F=A7=99: Here are some packages that I always install:\n\n") > ;; Ivy. > (widget-create 'checkbox > :notify > (lambda (widget &rest _) > (let ((val (widget-value widget))) > (setup-wizard--save-option-and-eval > 'ivy > (when val > `(progn > ,(setup-wizard--package-activate > 'ivy 'ivy-mode) > (setq enable-recursive-minibuffers t > ivy-use-selectable-prompt t > ivy-use-virtual-buffers t) > ,(setup-wizard--package-activate > 'counsel 'counsel-mode))) > "Install and enable =E2=80=98ivy-mode=E2=80=99 and = =E2=80=98counsel-mode=E2=80=99." > `(progn > (ivy-mode ,(if val 1 -1)) > (counsel-mode ,(if val 1 -1)))))) > :value nil) > (widget-insert > " Ivy: A completion package that makes typing file names, buffer > names, commands, etc so much easier.\n") > ;; Company > (widget-create 'checkbox > :notify > (lambda (widget &rest _) > (let ((val (widget-value widget))) > (setup-wizard--save-option-and-eval > 'company > (setup-wizard--package-activate > 'company 'company-mode) > "Install and enable =E2=80=98company-mode=E2=80=99." > `(company-mode ,(if val 1 -1))))) > :value nil) > (widget-insert > " Company: Popup completion menu when writing programs.\n") > (widget-create 'checkbox > :notify > (lambda (widget &rest _) > (let ((val (widget-value widget))) > (setup-wizard--save-option-and-eval > 'electric-pair-mode > (when val > `(electric-pair-mode)) > "Enable =E2=80=98electric-pair-mode=E2=80=99." > `(electric-pair-mode ,(if val 1 -1))))) > :value nil) > (widget-insert > " Electric-pair-mode (built-in): Automatically closes parenthesis\n") > (setup-wizard--insert "\n=F0=9F=A7=99: ...\n\n") > (setup-wizard--insert "=F0=9F=A7=99: I don=E2=80=99t use many packages.= \n")) > > ;;; Guide > > (defun setup-wizard--with-boilerplate > (setup-fn &optional page-list finish-fn) > "Call page setup function SETUP-FN with widget boilerplate. > PAGE-LIST is a list of setup function for pages to show in a > series. FINISH-FN is called when user clicks the finish button. > If PAGE-LIST or FINISH-FN are nil, don=E2=80=99t insert navigation > buttons." > (kill-all-local-variables) > (let ((inhibit-read-only t)) > (erase-buffer)) > (remove-overlays) > (funcall setup-fn) > (widget-insert "\n") > (when (and page-list finish-fn) > (setup-wizard--insert-step-buttons setup-fn page-list finish-fn)) > (use-local-map widget-keymap) > (widget-setup) > (goto-char (point-min)) > (local-set-key (kbd "q") #'setup-wizard--quit)) > > (defun setup-wizard--quit (&rest _) > "Quite the wizard." > (interactive) > (kill-buffer) > (message (with-temp-buffer > (setup-wizard--insert "=F0=9F=A7=9A: See ya!") > (buffer-string)))) > > (defun setup-wizard--insert-step-buttons (page page-list finish-fn) > "Insert buttons that go to previous and next page of PAGE. > PAGE-LIST is a list of setup function for pages to show in a series. > Insert a Button that calls FINISH-FN at the last page." > (let* ((idx (seq-position page-list page)) > (previous-page (if (eq idx 0) nil > (nth (1- idx) page-list))) > (next-page (nth (1+ idx) page-list))) > (setup-wizard--insert > (format "=F0=9F=A7=9A: We are at step %s/%s, what=E2=80=99s next? " > (1+ idx) (length page-list))) > (when previous-page > (widget-create > 'push-button > :notify (lambda (&rest _) > (setup-wizard--with-boilerplate > previous-page page-list finish-fn)) > :value "Back")) > (widget-insert " ") > (if next-page > (widget-create > 'push-button > :notify (lambda (&rest _) > (setup-wizard--with-boilerplate > next-page page-list finish-fn)) > :value "Next") > (widget-create > 'push-button > :notify (lambda (&rest _) (funcall finish-fn)) > :value "Finish")) > (widget-insert " ") > (widget-create > 'push-button > :value "Quit" > :notify #'setup-wizard--quit) > (widget-insert "\n"))) > > (defun setup-wizard--insert-config () > "Insert configuration in =E2=80=98setup-wizard--config=E2=80=99 line-by= -line." > (dolist (config (reverse setup-wizard--config)) > (insert ";; " (nth 2 config) "\n") > (dolist (conf (if (eq (car (nth 1 config)) 'progn) > (cdr (nth 1 config)) > (list (nth 1 config)))) > (insert (prin1-to-string conf) "\n")))) > > (defun setup-wizard--finish () > "The default finish function. > Constructs the config and display them." > (setup-wizard--with-boilerplate > (lambda () > (setup-wizard--insert > "=F0=9F=A7=9A: Here is your configuration! Do you want me to append= it to > init.el for you? ") > (widget-create 'push-button > :notify > (lambda (&rest _) > (let ((init-file (locate-user-emacs-file > "init.el" ".emacs"))) > (find-file init-file) > (goto-char (point-max)) > (insert "\n") > (setup-wizard--insert-config))) > :value "Append to init.el") > (widget-insert "\n\n") > (widget-insert > (with-temp-buffer > (setup-wizard--insert-config) > (emacs-lisp-mode) > (font-lock-fontify-region (point-min) (point-max)) > (buffer-string)))))) > > (defun setup-wizard () > "Run the setup wizard." > (interactive) > (switch-to-buffer (get-buffer-create "*mage tower*")) > (setq setup-wizard--config nil) > (let ((page-list '(setup-wizard--theme-page > setup-wizard--keybinding-page > setup-wizard--ui-features-page > setup-wizard--undo-page > setup-wizard--package-page))) > (setup-wizard--with-boilerplate > (car page-list) page-list > #'setup-wizard--finish))) > > ;;; Backport > > (unless (fboundp 'undo--last-change-was-undo-p) > (defun undo--last-change-was-undo-p (undo-list) > (while (and (consp undo-list) (eq (car undo-list) nil)) > (setq undo-list (cdr undo-list))) > (gethash undo-list undo-equiv-table))) > > (unless (fboundp 'undo-redo) > (defun undo-redo (&optional arg) > "Undo the last ARG undos." > (interactive "*p") > (cond > ((not (undo--last-change-was-undo-p buffer-undo-list)) > (user-error "No undo to undo")) > (t > (let* ((ul buffer-undo-list) > (new-ul > (let ((undo-in-progress t)) > (while (and (consp ul) (eq (car ul) nil)) > (setq ul (cdr ul))) > (primitive-undo arg ul))) > (new-pul (undo--last-change-was-undo-p new-ul))) > (message "Redo%s" (if undo-in-region " in region" "")) > (setq this-command 'undo) > (setq pending-undo-list new-pul) > (setq buffer-undo-list new-ul)))))) > > (unless (fboundp 'undo-only) > (defun undo-only (&optional arg) > "Undo some previous changes. > Repeat this command to undo more changes. > A numeric ARG serves as a repeat count. > Contrary to `undo', this will not redo a previous undo." > (interactive "*p") > (let ((undo-no-redo t)) (undo arg)))) > > (provide 'setup-wizard) > > ;;; setup-wizard.el ends here > --=20 Philip Kaludercic