generate-demo.sh
1 #!/bin/bash 2 # Generate demo screenshot/video for code-action-quick 3 # Usage: ./generate-demo.sh [screenshot|video] 4 # 5 # Requirements: 6 # - Emacs with emacsclient support 7 # - rust-analyzer for LSP 8 # - scrot or import (ImageMagick) for screenshots 9 # - ffmpeg for video recording 10 # - xdotool for window manipulation (optional) 11 12 set -e 13 14 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 15 PROJECT_DIR="$(dirname "$SCRIPT_DIR")" 16 FIXTURE_DIR="$PROJECT_DIR/test/fixtures/missing-import/src" 17 OUTPUT_DIR="$PROJECT_DIR/docs/assets" 18 MODE="${1:-screenshot}" 19 20 # Colors for output 21 RED='\033[0;31m' 22 GREEN='\033[0;32m' 23 YELLOW='\033[1;33m' 24 NC='\033[0m' # No Color 25 26 log() { echo -e "${GREEN}[demo]${NC} $1"; } 27 warn() { echo -e "${YELLOW}[warn]${NC} $1"; } 28 error() { echo -e "${RED}[error]${NC} $1"; exit 1; } 29 30 # Check dependencies 31 check_deps() { 32 log "Checking dependencies..." 33 command -v emacs >/dev/null || error "emacs not found" 34 command -v emacsclient >/dev/null || error "emacsclient not found" 35 command -v rust-analyzer >/dev/null || warn "rust-analyzer not found - demo may not work" 36 37 if [[ "$MODE" == "screenshot" ]]; then 38 if ! command -v scrot >/dev/null && ! command -v import >/dev/null; then 39 error "scrot or import (ImageMagick) required for screenshots" 40 fi 41 elif [[ "$MODE" == "video" ]]; then 42 command -v ffmpeg >/dev/null || error "ffmpeg required for video" 43 fi 44 } 45 46 # Create output directory 47 mkdir -p "$OUTPUT_DIR" 48 49 # Elisp code to set up the demo 50 DEMO_ELISP=$(cat << 'ELISP' 51 (progn 52 ;; Load the package 53 (add-to-list 'load-path (expand-file-name "..")) 54 (require 'code-action-quick) 55 56 ;; Configure for demo 57 (setq caq-indicator-delay 0.01) ; Fast updates for demo 58 (setq caq-debug nil) 59 60 ;; Set up a nice frame size 61 (set-frame-size (selected-frame) 100 30) 62 63 ;; Use a clean theme 64 (when (fboundp 'modus-vivendi) 65 (modus-vivendi)) 66 67 ;; Open the fixture file 68 (find-file "test/fixtures/missing-import/src/main.rs") 69 70 ;; Start eglot 71 (when (fboundp 'eglot-ensure) 72 (eglot-ensure)) 73 74 ;; Enable our mode 75 (code-action-quick-mode 1) 76 77 ;; Wait for LSP to initialize 78 (message "Waiting for LSP...") 79 (sleep-for 3) 80 81 ;; Move to the error location 82 (goto-char (point-min)) 83 (search-forward "HashMap" nil t) 84 (backward-word) 85 86 ;; Trigger indicator update 87 (sleep-for 0.5) 88 89 (message "Demo ready! Indicator should show: %s" caq--mode-line-indicator)) 90 ELISP 91 ) 92 93 # Take screenshot using available tool 94 take_screenshot() { 95 local output="$1" 96 local window_id="$2" 97 98 sleep 1 # Let the window settle 99 100 if command -v scrot >/dev/null; then 101 if [[ -n "$window_id" ]]; then 102 scrot -u "$output" 103 else 104 scrot "$output" 105 fi 106 elif command -v import >/dev/null; then 107 if [[ -n "$window_id" ]]; then 108 import -window "$window_id" "$output" 109 else 110 import -window root "$output" 111 fi 112 fi 113 114 log "Screenshot saved to $output" 115 } 116 117 # Record video 118 record_video() { 119 local output="$1" 120 local duration="${2:-10}" 121 local window_id="$3" 122 123 log "Recording for ${duration}s..." 124 125 if [[ -n "$window_id" ]]; then 126 # Get window geometry 127 eval $(xdotool getwindowgeometry --shell "$window_id") 128 ffmpeg -f x11grab -video_size "${WIDTH}x${HEIGHT}" -i ":0.0+${X},${Y}" \ 129 -t "$duration" -c:v libx264 -preset fast -y "$output" 2>/dev/null 130 else 131 # Record whole screen 132 ffmpeg -f x11grab -video_size 1920x1080 -i :0.0 \ 133 -t "$duration" -c:v libx264 -preset fast -y "$output" 2>/dev/null 134 fi 135 136 log "Video saved to $output" 137 } 138 139 # Main demo function 140 run_demo() { 141 log "Starting demo in $MODE mode..." 142 143 # Start Emacs server if not running 144 if ! emacsclient -e "t" &>/dev/null; then 145 log "Starting Emacs server..." 146 emacs --daemon 2>/dev/null || true 147 sleep 2 148 fi 149 150 # Create a new frame and run demo setup 151 log "Setting up demo..." 152 emacsclient -c -e "(progn (cd \"$PROJECT_DIR\") $DEMO_ELISP)" & 153 EMACS_PID=$! 154 155 # Wait for window to appear 156 sleep 5 157 158 # Find the Emacs window 159 WINDOW_ID="" 160 if command -v xdotool >/dev/null; then 161 WINDOW_ID=$(xdotool search --name "main.rs" | head -1) 162 if [[ -n "$WINDOW_ID" ]]; then 163 log "Found Emacs window: $WINDOW_ID" 164 xdotool windowactivate "$WINDOW_ID" 2>/dev/null || true 165 fi 166 fi 167 168 # Wait for LSP to fully initialize and show indicator 169 log "Waiting for LSP initialization..." 170 sleep 5 171 172 # Perform cursor movements to trigger indicator updates 173 log "Triggering indicator updates..." 174 emacsclient -e '(progn 175 (goto-char (point-min)) 176 (search-forward "HashMap" nil t) 177 (backward-word) 178 (redisplay t) 179 (sleep-for 1))' 2>/dev/null || true 180 181 sleep 2 182 183 case "$MODE" in 184 screenshot) 185 take_screenshot "$OUTPUT_DIR/demo-indicator.png" "$WINDOW_ID" 186 187 # Also take a screenshot showing code-action-quick-show 188 log "Taking screenshot of action list..." 189 emacsclient -e '(code-action-quick-show)' 2>/dev/null || true 190 sleep 1 191 take_screenshot "$OUTPUT_DIR/demo-action-list.png" "$WINDOW_ID" 192 ;; 193 video) 194 # Record a short demo video 195 log "Recording demo video..." 196 197 # Start recording in background 198 record_video "$OUTPUT_DIR/demo.mp4" 15 "$WINDOW_ID" & 199 RECORD_PID=$! 200 201 sleep 1 202 203 # Perform demo actions 204 emacsclient -e '(progn 205 ;; Show indicator updating 206 (goto-char (point-min)) 207 (sleep-for 1) 208 (search-forward "HashMap" nil t) 209 (backward-word) 210 (sleep-for 2) 211 212 ;; Show action list 213 (code-action-quick-show) 214 (sleep-for 2) 215 (keyboard-quit) 216 217 ;; Execute the action 218 (sleep-for 1) 219 (code-action-quick) 220 (sleep-for 2) 221 222 ;; Show the result 223 (goto-char (point-min)) 224 (sleep-for 2))' 2>/dev/null || true 225 226 wait $RECORD_PID 2>/dev/null || true 227 ;; 228 gif) 229 # Record and convert to GIF 230 log "Recording and converting to GIF..." 231 record_video "/tmp/demo-temp.mp4" 12 "$WINDOW_ID" 232 233 # Convert to GIF using ffmpeg 234 ffmpeg -i "/tmp/demo-temp.mp4" \ 235 -vf "fps=10,scale=800:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \ 236 -loop 0 "$OUTPUT_DIR/demo.gif" -y 2>/dev/null 237 238 rm -f "/tmp/demo-temp.mp4" 239 log "GIF saved to $OUTPUT_DIR/demo.gif" 240 ;; 241 esac 242 243 # Cleanup 244 log "Cleaning up..." 245 emacsclient -e '(delete-frame)' 2>/dev/null || true 246 247 log "Demo complete!" 248 log "Output files in: $OUTPUT_DIR" 249 ls -la "$OUTPUT_DIR" 250 } 251 252 # Headless screenshot mode (for CI) 253 run_headless() { 254 log "Running headless demo..." 255 256 # Use Xvfb for headless operation 257 if ! command -v Xvfb >/dev/null; then 258 error "Xvfb required for headless mode" 259 fi 260 261 # Start virtual display 262 Xvfb :99 -screen 0 1280x720x24 & 263 XVFB_PID=$! 264 export DISPLAY=:99 265 266 sleep 2 267 268 # Run the demo 269 run_demo 270 271 # Cleanup 272 kill $XVFB_PID 2>/dev/null || true 273 } 274 275 # Show usage 276 usage() { 277 cat << EOF 278 Usage: $0 [mode] 279 280 Modes: 281 screenshot Take a screenshot of the indicator (default) 282 video Record a short demo video (MP4) 283 gif Record and convert to animated GIF 284 headless Run in headless mode (requires Xvfb) 285 286 Examples: 287 $0 screenshot 288 $0 gif 289 $0 headless screenshot 290 291 EOF 292 } 293 294 # Parse arguments 295 case "$1" in 296 screenshot|video|gif) 297 check_deps 298 run_demo 299 ;; 300 headless) 301 MODE="${2:-screenshot}" 302 check_deps 303 run_headless 304 ;; 305 -h|--help) 306 usage 307 ;; 308 *) 309 if [[ -n "$1" ]]; then 310 error "Unknown mode: $1" 311 fi 312 check_deps 313 run_demo 314 ;; 315 esac