fix: lsp issues resolved, fix reminders triggered after sleep

This commit is contained in:
Michael Freno
2026-01-09 09:56:21 -05:00
parent 54ebe6789a
commit 3d7044050e
4 changed files with 101 additions and 34 deletions

View File

@@ -7,16 +7,18 @@
import Foundation import Foundation
struct TimerState: Equatable { struct TimerState {
let type: TimerType let type: TimerType
var remainingSeconds: Int var remainingSeconds: Int
var isPaused: Bool var isPaused: Bool
var isActive: Bool var isActive: Bool
var targetDate: Date
init(type: TimerType, intervalSeconds: Int, isPaused: Bool = false, isActive: Bool = true) { init(type: TimerType, intervalSeconds: Int, isPaused: Bool = false, isActive: Bool = true) {
self.type = type self.type = type
self.remainingSeconds = intervalSeconds self.remainingSeconds = intervalSeconds
self.isPaused = isPaused self.isPaused = isPaused
self.isActive = isActive self.isActive = isActive
self.targetDate = Date().addingTimeInterval(Double(intervalSeconds))
} }
} }

View File

@@ -5,24 +5,24 @@
// Created by Mike Freno on 1/7/26. // Created by Mike Freno on 1/7/26.
// //
import Foundation
import Combine import Combine
import Foundation
@MainActor @MainActor
class TimerEngine: ObservableObject { class TimerEngine: ObservableObject {
@Published var timerStates: [TimerType: TimerState] = [:] @Published var timerStates: [TimerType: TimerState] = [:]
@Published var activeReminder: ReminderEvent? @Published var activeReminder: ReminderEvent?
private var timerSubscription: AnyCancellable? private var timerSubscription: AnyCancellable?
private let settingsManager: SettingsManager private let settingsManager: SettingsManager
init(settingsManager: SettingsManager) { init(settingsManager: SettingsManager) {
self.settingsManager = settingsManager self.settingsManager = settingsManager
} }
func start() { func start() {
stop() stop()
for timerType in TimerType.allCases { for timerType in TimerType.allCases {
let config = settingsManager.timerConfiguration(for: timerType) let config = settingsManager.timerConfiguration(for: timerType)
if config.enabled { if config.enabled {
@@ -34,7 +34,7 @@ class TimerEngine: ObservableObject {
) )
} }
} }
timerSubscription = Timer.publish(every: 1.0, on: .main, in: .common) timerSubscription = Timer.publish(every: 1.0, on: .main, in: .common)
.autoconnect() .autoconnect()
.sink { [weak self] _ in .sink { [weak self] _ in
@@ -43,25 +43,25 @@ class TimerEngine: ObservableObject {
} }
} }
} }
func stop() { func stop() {
timerSubscription?.cancel() timerSubscription?.cancel()
timerSubscription = nil timerSubscription = nil
timerStates.removeAll() timerStates.removeAll()
} }
func pause() { func pause() {
for (type, _) in timerStates { for (type, _) in timerStates {
timerStates[type]?.isPaused = true timerStates[type]?.isPaused = true
} }
} }
func resume() { func resume() {
for (type, _) in timerStates { for (type, _) in timerStates {
timerStates[type]?.isPaused = false timerStates[type]?.isPaused = false
} }
} }
func skipNext(type: TimerType) { func skipNext(type: TimerType) {
guard let state = timerStates[type] else { return } guard let state = timerStates[type] else { return }
let config = settingsManager.timerConfiguration(for: type) let config = settingsManager.timerConfiguration(for: type)
@@ -72,55 +72,69 @@ class TimerEngine: ObservableObject {
isActive: state.isActive isActive: state.isActive
) )
} }
func dismissReminder() { func dismissReminder() {
guard let reminder = activeReminder else { return } guard let reminder = activeReminder else { return }
activeReminder = nil activeReminder = nil
skipNext(type: reminder.type) skipNext(type: reminder.type)
if case .lookAwayTriggered = reminder { if case .lookAwayTriggered = reminder {
resume() resume()
} }
} }
private func handleTick() { private func handleTick() {
guard activeReminder == nil else { return } guard activeReminder == nil else { return }
for (type, state) in timerStates { for (type, state) in timerStates {
guard state.isActive && !state.isPaused else { continue } guard state.isActive && !state.isPaused else { continue }
// prevent overshoot - in case user closes laptop while timer is running, we don't want to
// trigger on open,
if state.targetDate < Date() - 3.0 { // slight grace
// Reset the timer when it has overshot its interval
let config = settingsManager.timerConfiguration(for: type)
timerStates[type] = TimerState(
type: type,
intervalSeconds: config.intervalSeconds,
isPaused: state.isPaused,
isActive: state.isActive
)
continue // Skip normal countdown logic after reset
}
timerStates[type]?.remainingSeconds -= 1 timerStates[type]?.remainingSeconds -= 1
if let updatedState = timerStates[type], updatedState.remainingSeconds <= 0 { if let updatedState = timerStates[type], updatedState.remainingSeconds <= 0 {
triggerReminder(for: type) triggerReminder(for: type)
break break
} }
} }
} }
func triggerReminder(for type: TimerType) { func triggerReminder(for type: TimerType) {
switch type { switch type {
case .lookAway: case .lookAway:
pause() pause()
activeReminder = .lookAwayTriggered(countdownSeconds: settingsManager.settings.lookAwayCountdownSeconds) activeReminder = .lookAwayTriggered(
countdownSeconds: settingsManager.settings.lookAwayCountdownSeconds)
case .blink: case .blink:
activeReminder = .blinkTriggered activeReminder = .blinkTriggered
case .posture: case .posture:
activeReminder = .postureTriggered activeReminder = .postureTriggered
} }
} }
func getTimeRemaining(for type: TimerType) -> TimeInterval { func getTimeRemaining(for type: TimerType) -> TimeInterval {
guard let state = timerStates[type] else { return 0 } guard let state = timerStates[type] else { return 0 }
return TimeInterval(state.remainingSeconds) return TimeInterval(state.remainingSeconds)
} }
func getFormattedTimeRemaining(for type: TimerType) -> String { func getFormattedTimeRemaining(for type: TimerType) -> String {
let seconds = Int(getTimeRemaining(for: type)) let seconds = Int(getTimeRemaining(for: type))
let minutes = seconds / 60 let minutes = seconds / 60
let remainingSeconds = seconds % 60 let remainingSeconds = seconds % 60
if minutes >= 60 { if minutes >= 60 {
let hours = minutes / 60 let hours = minutes / 60
let remainingMinutes = minutes % 60 let remainingMinutes = minutes % 60

19
buildServer.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "xcode build server",
"version": "1.3.0",
"bspVersion": "2.2.0",
"languages": [
"c",
"cpp",
"objective-c",
"objective-cpp",
"swift"
],
"argv": [
"/opt/homebrew/bin/xcode-build-server"
],
"workspace": "/Users/mike/Code/Gaze/Gaze.xcodeproj/project.xcworkspace",
"build_root": "/Users/mike/Library/Developer/Xcode/DerivedData/Gaze-ahifnboyyfcsskfhqfbfzxarhabs",
"scheme": "Gaze",
"kind": "xcode"
}

52
run
View File

@@ -1,13 +1,10 @@
#!/bin/bash #!/bin/bash
# Build and run Gaze application # Build and run Gaze application
# Usage: ./run [build|test|run] # Usage: ./run [build|test|run]
# Default action is build and run # Default action is build and run
ACTION=${1:-run} ACTION=${1:-run}
VERBOSE=false VERBOSE=false
OUTPUT_FILE="" OUTPUT_FILE=""
# Function to kill any existing Gaze processes # Function to kill any existing Gaze processes
kill_existing_gaze_processes() { kill_existing_gaze_processes() {
echo "🔍 Checking for existing Gaze processes..." echo "🔍 Checking for existing Gaze processes..."
@@ -23,7 +20,25 @@ kill_existing_gaze_processes() {
echo "✅ No existing Gaze processes found" echo "✅ No existing Gaze processes found"
fi fi
} }
# Function to update LSP configuration
update_lsp_config() {
echo "🔧 Updating LSP configuration..."
# Check if xcode-build-server is installed
if command -v xcode-build-server &> /dev/null; then
# Generate buildServer.json for LSP
xcode-build-server config -project Gaze.xcodeproj -scheme Gaze > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "✅ LSP configuration updated (buildServer.json created)"
else
echo "⚠️ Could not update LSP configuration"
fi
else
echo "⚠️ xcode-build-server not found. Install with: brew install xcode-build-server"
echo " This helps Neovim's LSP recognize your Swift modules"
fi
}
# Parse command line arguments # Parse command line arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
@@ -36,13 +51,16 @@ while [[ $# -gt 0 ]]; do
VERBOSE=true VERBOSE=true
shift 2 shift 2
;; ;;
--no-lsp)
SKIP_LSP=true
shift
;;
*) *)
ACTION="$1" ACTION="$1"
shift shift
;; ;;
esac esac
done done
# Function to run command with output control # Function to run command with output control
run_with_output() { run_with_output() {
local cmd="$1" local cmd="$1"
@@ -60,9 +78,7 @@ run_with_output() {
eval "$cmd" > /dev/null 2>&1 eval "$cmd" > /dev/null 2>&1
fi fi
} }
echo "=== Gaze Application Script ===" echo "=== Gaze Application Script ==="
if [ "$ACTION" = "build" ]; then if [ "$ACTION" = "build" ]; then
echo "Building Gaze project..." echo "Building Gaze project..."
run_with_output "xcodebuild -project Gaze.xcodeproj -scheme Gaze -configuration Debug build" run_with_output "xcodebuild -project Gaze.xcodeproj -scheme Gaze -configuration Debug build"
@@ -70,6 +86,11 @@ if [ "$ACTION" = "build" ]; then
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
echo "✅ Build succeeded!" echo "✅ Build succeeded!"
echo "💡 The app is located at: build/Debug/Gaze.app" echo "💡 The app is located at: build/Debug/Gaze.app"
# Update LSP config after successful build
if [ "$SKIP_LSP" != true ]; then
update_lsp_config
fi
else else
echo "❌ Build failed!" echo "❌ Build failed!"
exit 1 exit 1
@@ -85,7 +106,6 @@ elif [ "$ACTION" = "test" ]; then
echo "❌ Tests failed!" echo "❌ Tests failed!"
exit 1 exit 1
fi fi
elif [ "$ACTION" = "run" ]; then elif [ "$ACTION" = "run" ]; then
echo "Building and running Gaze application..." echo "Building and running Gaze application..."
@@ -97,6 +117,12 @@ elif [ "$ACTION" = "run" ]; then
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
echo "✅ Build succeeded!" echo "✅ Build succeeded!"
# Update LSP config after successful build
if [ "$SKIP_LSP" != true ]; then
update_lsp_config
fi
# Get the actual build output directory from xcodebuild # Get the actual build output directory from xcodebuild
BUILD_DIR="$(xcodebuild -project Gaze.xcodeproj -scheme Gaze -configuration Debug -showBuildSettings 2>/dev/null | grep -m 1 "BUILT_PRODUCTS_DIR" | sed 's/.*= //')" BUILD_DIR="$(xcodebuild -project Gaze.xcodeproj -scheme Gaze -configuration Debug -showBuildSettings 2>/dev/null | grep -m 1 "BUILT_PRODUCTS_DIR" | sed 's/.*= //')"
APP_PATH="${BUILD_DIR}/Gaze.app" APP_PATH="${BUILD_DIR}/Gaze.app"
@@ -113,17 +139,23 @@ elif [ "$ACTION" = "run" ]; then
echo "❌ Build failed!" echo "❌ Build failed!"
exit 1 exit 1
fi fi
elif [ "$ACTION" = "lsp" ]; then
# New command to just update LSP config
echo "Updating LSP configuration only..."
update_lsp_config
else else
echo "Usage: $0 [build|test|run] [-v|--verbose] [-o|--output <file_name>]" echo "Usage: $0 [build|test|run|lsp] [-v|--verbose] [-o|--output <file_name>] [--no-lsp]"
echo "" echo ""
echo "Commands:" echo "Commands:"
echo " build - Build the application" echo " build - Build the application"
echo " test - Run unit tests" echo " test - Run unit tests"
echo " run - Build and run the application (default)" echo " run - Build and run the application (default)"
echo " lsp - Update LSP configuration only (buildServer.json)"
echo "" echo ""
echo "Options:" echo "Options:"
echo " -v, --verbose - Show output in stdout" echo " -v, --verbose - Show output in stdout"
echo " -o, --output - Write output to log file" echo " -o, --output - Write output to log file"
echo " --no-lsp - Skip LSP configuration update"
exit 1 exit 1
fi fi