/ scripts / generate-demo.sh
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