pw_watch: Handle EOFError from Ctrl-Z on Windows

On Windows, hitting Ctrl-C can genererate both KeyboardInterrupt and
EOFError due to long-standing Python async problems. This extends the
watcher to gracefully handle this case, avoiding giving the user an ugly
error when quitting in normal watcher operation.

Additionally, this increases the scope of the interrupt handler to also
gracefully exit when hitting Ctrl-C or Ctrl-Z during watcher startup,
when waiting for watchdog to attach all the event handlers.

Change-Id: I2ddb9995d4c97d70eaba139d26bef8f8a4c6235e
diff --git a/pw_watch/py/pw_watch/debounce.py b/pw_watch/py/pw_watch/debounce.py
index 87a2ff6..dd9005d 100644
--- a/pw_watch/py/pw_watch/debounce.py
+++ b/pw_watch/py/pw_watch/debounce.py
@@ -152,7 +152,9 @@
                     self.function.on_complete(cancelled=True)
                     self._transition(State.RERUN)
                 self._start_cooldown_timer()
-        except KeyboardInterrupt:
+        # Ctrl-C on Unix generates KeyboardInterrupt
+        # Ctrl-Z on Windows generates EOFError
+        except (KeyboardInterrupt, EOFError):
             self.function.on_keyboard_interrupt()
 
     def _start_cooldown_timer(self):
@@ -174,5 +176,7 @@
                     self._press_unlocked('Rerunning: %s' %
                                          self.rerun_event_description)
 
-        except KeyboardInterrupt:
+        # Ctrl-C on Unix generates KeyboardInterrupt
+        # Ctrl-Z on Windows generates EOFError
+        except (KeyboardInterrupt, EOFError):
             self.function.on_keyboard_interrupt()
diff --git a/pw_watch/py/pw_watch/watch.py b/pw_watch/py/pw_watch/watch.py
index 05817b5..01744e8 100755
--- a/pw_watch/py/pw_watch/watch.py
+++ b/pw_watch/py/pw_watch/watch.py
@@ -424,25 +424,29 @@
         charset=charset,
     )
 
-    observer = Observer()
-    observer.schedule(
-        event_handler,
-        path_of_directory_to_watch,
-        recursive=True,
-    )
-    observer.start()
-
-    _LOG.info('Directory to watch: %s', path_to_log)
-    _LOG.info('Watching for file changes. Ctrl-C exits.')
-
-    event_handler.debouncer.press('Triggering initial build...')
-
     try:
+        # It can take awhile to configure the filesystem watcher, so have the
+        # message reflect that with the "...". Run inside the try: to
+        # gracefully handle the user Ctrl-C'ing out during startup.
+        _LOG.info('Attaching filesystem watcher to %s/...', path_to_log)
+        observer = Observer()
+        observer.schedule(
+            event_handler,
+            path_of_directory_to_watch,
+            recursive=True,
+        )
+        observer.start()
+
+        event_handler.debouncer.press('Triggering initial build...')
+
         while observer.isAlive():
             observer.join(1)
-    except KeyboardInterrupt:
+    # Ctrl-C on Unix generates KeyboardInterrupt
+    # Ctrl-Z on Windows generates EOFError
+    except (KeyboardInterrupt, EOFError):
         _exit_due_to_interrupt()
 
+    _LOG.critical('Should never get here')
     observer.join()