anchor.diff:              })
   
       member this.Dispatcher = function
  -        | KeyPress (chord, handler) -> Async <| this.KeyPress chord handler.Handle
  +        | KeyPress (chord, handler) -> Async <| this.KeyPress this.Dispatcher chord handler.Handle
           | CursorUp -> Sync <| MainLogic.toMutation (fun m -> m.WithCursorRel -1)
           | CursorUpHalfPage -> Sync <| MainLogic.toMutation (fun m -> m.WithCursorRel -m.HalfPageSize)
           | CursorDown -> Sync <| MainLogic.toMutation (fun m -> m.WithCursorRel 1)
  
positive.code:             return { model with Status = Some <| MainStatus.sort field desc }
          }
  
      let initModel (config: Config) (fsReader: IFileSystemReader) startOptions isFirstInstance model =
          let defaultPath = config.DefaultPath |> Path.Parse |? Path.Root
          let startPath =
              startOptions.StartPath |? (
                  match config.StartPath with
                  | RestorePrevious -> config.PreviousPath
                  | DefaultPath -> config.DefaultPath
              )
          { loadConfig fsReader config model with
              Location =
                  match config.StartPath with
                  | RestorePrevious -> defaultPath
                  | DefaultPath -> config.PreviousPath |> Path.Parse |? defaultPath
              WindowLocation =
                  startOptions.StartLocation |> Option.defaultWith (fun () ->
                      if isFirstInstance then (config.Window.Left, config.Window.Top)
                      else (config.Window.Left + 30, config.Window.Top + 30))
              WindowSize = startOptions.StartSize |? (config.Window.Width, config.Window.Height)
          } |> Navigation.openUserPath fsReader startPath
  
      module Cursor =
          let private moveCursorToNext predicate reverse model =
  
negative.code:                 yield! Navigation.refresh fsReader model |> Result.map (performedAction action)
          }
  
          let recycle fsReader fsWriter (config: Config) (model: MainModel) = asyncSeqResult {
              if model.SelectedNode.Type = NetHost then
                  let host = model.SelectedNode.Name
                  config.RemoveNetHost host
                  config.Save()
                  let! model = Navigation.refresh fsReader model
                  yield { model with Status = Some <| MainStatus.removedNetworkHost host }
              else
                  yield! delete fsReader fsWriter model.SelectedNode false model
          }
  
          let undo fsReader fsWriter model = asyncSeqResult {
              match model.UndoStack with
              | action :: rest ->
                  let model = { model with UndoStack = rest }
                  let! model =
                      match action with
                      | CreatedItem node ->
                          undoCreate fsReader fsWriter node model
                      | RenamedItem (oldNode, curName) ->
                          undoRename fsReader fsWriter oldNode curName model
                          |> AsyncSeq.singleton
                      | MovedItem (node, newPath) ->
                          undoMove fsReader fsWriter node newPath model
                      | CopiedItem (node, newPath) ->
                          undoCopy fsReader fsWriter node newPath model
                      | DeletedItem (node, permanent) ->
                          Error (CannotUndoDelete (permanent, node))
                          |> AsyncSeq.singleton
                  yield
                      { model with
                          RedoStack = action :: model.RedoStack
                          Status = Some <| MainStatus.undoAction action model.PathFormat
                      }
              | [] -> return NoUndoActions
          }
  
          let redo fsReader fsWriter model = asyncSeqResult {
              match model.RedoStack with
              | action :: rest ->
                  let model = { model with RedoStack = rest }
                  let goToPath (nodePath: Path) =
                      let path = nodePath.Parent
                      if path <> model.Location then
                          Navigation.openPath fsReader path SelectNone model
                      else Ok model
                  let! model = asyncSeqResult {
                      match action with
                      | CreatedItem node ->
                          let! model = goToPath node.Path
                          yield! create fsReader fsWriter node.Type node.Name model
                      | RenamedItem (node, newName) ->
                          let! model = goToPath node.Path
                          yield! rename fsReader fsWriter node newName model
                      | MovedItem (node, newPath)
                      | CopiedItem (node, newPath) ->
                          let! model = goToPath newPath
                          let moveOrCopy =
                              match action with
                              | MovedItem _ -> Move
                              | _ -> Copy
                          yield { model with Status = MainStatus.redoingAction action model.PathFormat }
                          yield! putItem fsReader fsWriter false node moveOrCopy model
                      | DeletedItem (node, permanent) ->
                          let! model = goToPath node.Path
                          yield { model with Status = MainStatus.redoingAction action model.PathFormat }
                          yield! delete fsReader fsWriter node permanent model
                  }
                  yield
                      { model with
                          RedoStack = rest
                          Status = Some <| MainStatus.redoAction action model.PathFormat
                      }
              | [] -> return NoRedoActions
          }
  
          let openSplitScreenWindow (os: IOperatingSystem) getScreenBounds model = result {
              let mapFst f t = (fst t |> f, snd t)
              let fitRect = Rect.ofPairs model.WindowLocation (model.WindowSize |> mapFst ((*) 2))
                            |> Rect.fit (getScreenBounds())
              let model =
                  { model with
                      WindowLocation = fitRect.Location
                      WindowSize = fitRect.Size |> mapFst (flip (/) 2)
                  }
  
              let left, top = model.WindowLocation
              let width, height = model.WindowSize
              let left = left + width
              let args = sprintf "\"%s\" --location=%i,%i --size=%i,%i"
                                 (model.Location.Format Windows) left top width height
  
              let! koffeePath = Path.Parse (System.Reflection.Assembly.GetExecutingAssembly().Location)
                                |> Result.ofOption CouldNotFindKoffeeExe
              let folder = koffeePath.Parent
              do! os.LaunchApp (koffeePath.Format Windows) folder args
                  |> Result.mapError (fun e -> CouldNotOpenApp ("Koffee", e))
              return model
          }
  
          let openExplorer (os: IOperatingSystem) (model: MainModel) =
              os.OpenExplorer model.SelectedNode
              { model with Status = Some <| MainStatus.openExplorer }
  
          let openCommandLine (os: IOperatingSystem) (config: Config) model = result {
              if model.Location <> Path.Root then
                  do! os.LaunchApp config.CommandlinePath model.Location ""
                      |> Result.mapError (fun e -> CouldNotOpenApp ("Commandline tool", e))
                  return { model with Status = Some <| MainStatus.openCommandLine model.LocationFormatted }
              else return model
          }
  
          let openWithTextEditor (os: IOperatingSystem) (config: Config) (model: MainModel) = result {
              match model.SelectedNode.Type with
              | File ->
                  let args = model.SelectedNode.Path.Format Windows |> sprintf "\"%s\""
                  do! os.LaunchApp config.TextEditor model.Location args
                      |> Result.mapError (fun e -> CouldNotOpenApp ("Text Editor", e))
                  return { model with Status = Some <| MainStatus.openTextEditor model.SelectedNode.Name }
              | _ -> return model
          }
  
          let openSettings fsReader (settingsFactory: unit -> Mvc<SettingsEvents, SettingsModel>) (config: Config) model = result {
              settingsFactory().StartDialog() |> ignore
              return!
                  loadConfig fsReader config model
                  |> Navigation.refresh fsReader
          }
  
  
      let initModel (config: Config) (fsReader: IFileSystemReader) startOptions isFirstInstance model =
          let defaultPath = config.DefaultPath |> Path.Parse |? Path.Root
          let startPath =
              startOptions.StartPath |? (
                  match config.StartPath with
                  | RestorePrevious -> config.PreviousPath
                  | DefaultPath -> config.DefaultPath
              )
          { loadConfig fsReader config model with
              Location =
                  match config.StartPath with
                  | RestorePrevious -> defaultPath
                  | DefaultPath -> config.PreviousPath |> Path.Parse |? defaultPath
              WindowLocation =
                  startOptions.StartLocation |> Option.defaultWith (fun () ->
                      if isFirstInstance then (config.Window.Left, config.Window.Top)
                      else (config.Window.Left + 30, config.Window.Top + 30))
              WindowSize = startOptions.StartSize |? (config.Window.Width, config.Window.Height)
          } |> Navigation.openUserPath fsReader startPath
  
      let submitInput fsReader fsWriter (config: Config) model = asyncSeqResult {
          let mode = model.InputMode
          let model = { model with InputMode = None }
          match mode with
          | Some (Input Search) ->
              let! search, caseSensitive = Cursor.parseSearch model.InputText
              let caseSensitive = caseSensitive |? config.SearchCaseSensitive
              yield Cursor.search caseSensitive search false model
          | Some (Input CreateFile) ->
              yield! Action.create fsReader fsWriter File model.InputText model
          | Some (Input CreateFolder) ->
              yield! Action.create fsReader fsWriter Folder model.InputText model
          | Some (Input (Rename _)) ->
              yield! Action.rename fsReader fsWriter model.SelectedNode model.InputText model
          | _ ->
              yield model
      }
  
      let inputCharTyped fsReader fsWriter (config: Config) char model = asyncSeqResult {
          let withBookmark char model =
              let winPath = model.Location.Format Windows
              config.SetBookmark char winPath
              config.Save()
              { model with Status = Some <| MainStatus.setBookmark char winPath }
          let mode = model.InputMode
          let model = { model with InputMode = None }
          match mode with
          | Some (Prompt mode) ->
              match mode with
              | Find caseSensitive ->
                  yield Cursor.find caseSensitive char model
              | GoToBookmark ->
                  match config.GetBookmark char with
                  | Some path ->
                      yield! Navigation.openUserPath fsReader path model
                  | None ->
                      yield { model with Status = Some <| MainStatus.noBookmark char }
              | SetBookmark ->
                  match config.GetBookmark char |> Option.bind Path.Parse with
                  | Some existingPath ->
                      yield
                          { model with
                              InputMode = Some (Confirm (OverwriteBookmark (char, existingPath)))
                              InputText = ""
                          }
                  | None -> 
                      yield withBookmark char model
              | DeleteBookmark ->
                  match config.GetBookmark char with
                  | Some path ->
                      config.RemoveBookmark char
                      config.Save()
                      yield { model with Status = Some <| MainStatus.deletedBookmark char path }
                  | None ->
                      yield { model with Status = Some <| MainStatus.noBookmark char }
          | Some (Confirm confirmType) ->
              match char with
              | 'y' ->
                  match confirmType with
                  | Overwrite (action, src, _) ->
                      let! model = Action.putItem fsReader fsWriter true src action model
                      yield { model with YankRegister = None }
                  | Delete ->
                      yield! Action.delete fsReader fsWriter model.SelectedNode true model
                  | OverwriteBookmark (char, _) ->
                      yield withBookmark char model
              | 'n' ->
                  let model = { model with Status = Some <| MainStatus.cancelled }
                  match confirmType with
                  | Overwrite _ when not model.ShowHidden && model.Nodes |> Seq.exists (fun n -> n.IsHidden) ->
                      // if we were temporarily showing hidden files, refresh
                      yield! Navigation.refresh fsReader model
                  | _ ->
                      yield model
              | _ -> ()
          | _ -> ()
      }
  
  
  type MainController(fsReader: IFileSystemReader,
                      fsWriter: IFileSystemWriter,
  