<commit_before>/**
 * <NAME> - http://taewon.de - @hataewon
 * See LICENSE
 */

import Cocoa
import PureLayout
import RxSwift

enum MainWindowAction {

  case becomeKey(mainWindow: MainWindowComponent)
  case openQuickly(mainWindow: MainWindowComponent)
  case changeCwd(mainWindow: MainWindowComponent, cwd: URL)
  case changeBufferList(mainWindow: MainWindowComponent, buffers: [NeoVimBuffer])
  case changeFileBrowserSelection(mainWindow: MainWindowComponent, url: URL)
  case close(mainWindow: MainWindowComponent, mainWindowPrefData: MainWindowPrefData)
}

struct MainWindowPrefData: StandardPrefData {

  fileprivate static let isAllToolsVisible = "is-all-tools-visible"
  fileprivate static let isToolButtonsVisible = "is-tool-buttons-visible"
  fileprivate static let toolPrefDatas = "tool-pref-datas"

  static let `default` = MainWindowPrefData(isAllToolsVisible: true,
                                            isToolButtonsVisible: true,
                                            toolPrefDatas: [
                                                ToolPrefData.defaults[.fileBrowser]!,
                                                ToolPrefData.defaults[.bufferList]!,
                                            ])

  var isAllToolsVisible: Bool
  var isToolButtonsVisible: Bool
  var toolPrefDatas: [ToolPrefData]

  init(isAllToolsVisible: Bool, isToolButtonsVisible: Bool, toolPrefDatas: [ToolPrefData]) {
    self.isAllToolsVisible = isAllToolsVisible
    self.isToolButtonsVisible = isToolButtonsVisible
    self.toolPrefDatas = toolPrefDatas
  }

  init?(dict: [String: Any]) {

    guard let isAllToolsVisible = PrefUtils.bool(from: dict, for: MainWindowPrefData.isAllToolsVisible),
          let isToolButtonsVisible = PrefUtils.bool(from: dict, for: MainWindowPrefData.isToolButtonsVisible),
          let toolDataDicts = dict[MainWindowPrefData.toolPrefDatas] as? [[String: Any]]
        else {
      return nil
    }

    // Add default tool pref data for missing identifiers.
    let toolDatas = toolDataDicts.map { ToolPrefData(dict: $0) }.flatMap { $0 }
    let missingToolDatas = Set(ToolIdentifier.all)
        .subtracting(toolDatas.map { $0.identifier })
        .map { ToolPrefData.defaults[$0] }
        .flatMap { $0 }

    self.init(isAllToolsVisible: isAllToolsVisible,
              isToolButtonsVisible: isToolButtonsVisible,
              toolPrefDatas: [ toolDatas, missingToolDatas ].flatMap { $0 })
  }

  func dict() -> [String: Any] {
    return [
      MainWindowPrefData.isAllToolsVisible: self.isAllToolsVisible,
      MainWindowPrefData.isToolButtonsVisible: self.isToolButtonsVisible,
      MainWindowPrefData.toolPrefDatas: self.toolPrefDatas.map { $0.dict() },
    ]
  }

  func toolPrefData(for identifier: ToolIdentifier) -> ToolPrefData {
    guard let tool = self.toolPrefDatas.filter({ $0.identifier == identifier }).first else {
      preconditionFailure("[ERROR] No tool for \(identifier) found!")
    }

    return tool
  }
}

class MainWindowComponent: WindowComponent,
                           NeoVimViewDelegate,
                           NSWindowDelegate,
                           NSUserInterfaceValidations,
                           WorkspaceDelegate
{

  fileprivate static let nibName = "MainWindow"

  fileprivate var defaultEditorFont: NSFont

  fileprivate var _cwd: URL = FileUtils.userHomeUrl

  fileprivate let fontManager = NSFontManager.shared()
  fileprivate let fileItemService: FileItemService

  fileprivate let workspace: Workspace
  fileprivate let neoVimView: NeoVimView
  fileprivate var tools = [ToolIdentifier: WorkspaceToolComponent]()

  // MARK: - API
  var uuid: String {
    return self.neoVimView.uuid
  }

  var cwd: URL {
    get {
      self._cwd = self.neoVimView.cwd
      return self._cwd
    }

    set {
      let oldValue = self._cwd
      if oldValue == newValue {
        return
      }

      self._cwd = newValue
      self.neoVimView.cwd = newValue
      self.fileItemService.unmonitor(url: oldValue)
      self.fileItemService.monitor(url: newValue)
    }
  }

  // TODO: Consider an option object for cwd, urls, etc...
  /**
   The init() method does not show the window. Call MainWindowComponent.show() to do so.
   */
  init(source: Observable<Any>,
       fileItemService: FileItemService,
       cwd: URL,
       urls: [URL] = [],
       initialData: PrefData)
  {
    self.neoVimView = NeoVimView(frame: CGRect.zero,
                                 config: NeoVimView.Config(useInteractiveZsh: initialData.advanced.useInteractiveZsh))
    self.neoVimView.configureForAutoLayout()

    self.workspace = Workspace(mainView: self.neoVimView)

    self.defaultEditorFont = initialData.appearance.editorFont
    self.fileItemService = fileItemService
    self._cwd = cwd

    super.init(source: source, nibName: MainWindowComponent.nibName)

    self.window.delegate = self
    self.workspace.delegate = self

    self.setupTools(with: initialData.mainWindow)

    self.neoVimView.delegate = self
    self.neoVimView.font = self.defaultEditorFont
    self.neoVimView.usesLigatures = initialData.appearance.editorUsesLigatures
    self.neoVimView.linespacing = initialData.appearance.editorLinespacing
    let neoVimViewCwd = self.neoVimView.cwd
    if neoVimViewCwd == cwd {
      self.fileItemService.monitor(url: cwd)
    } else {
      self.neoVimView.cwd = cwd // This will publish the MainWindowAction.changeCwd action for the file browser.
    }
    self.neoVimView.open(urls: urls)

    self.addReactions()

    self.window.makeFirstResponder(self.neoVimView)
  }

  fileprivate func setupTools(with mainWindowData: MainWindowPrefData) {
    // By default the tool buttons are shown and only the file browser tool is shown.
    let fileBrowserToolData = mainWindowData.toolPrefData(for: .fileBrowser)
    let bufferListToolData = mainWindowData.toolPrefData(for: .bufferList)<commit_msg>GH-369 set cwd in main window component later such that the cwdChanged callback can execute normally<commit_after>/**
 * <NAME> - http://taewon.de - @hataewon
 * See LICENSE
 */

import Cocoa
import PureLayout
import RxSwift

enum MainWindowAction {

  case becomeKey(mainWindow: MainWindowComponent)
  case openQuickly(mainWindow: MainWindowComponent)
  case changeCwd(mainWindow: MainWindowComponent, cwd: URL)
  case changeBufferList(mainWindow: MainWindowComponent, buffers: [NeoVimBuffer])
  case changeFileBrowserSelection(mainWindow: MainWindowComponent, url: URL)
  case close(mainWindow: MainWindowComponent, mainWindowPrefData: MainWindowPrefData)
}

struct MainWindowPrefData: StandardPrefData {

  fileprivate static let isAllToolsVisible = "is-all-tools-visible"
  fileprivate static let isToolButtonsVisible = "is-tool-buttons-visible"
  fileprivate static let toolPrefDatas = "tool-pref-datas"

  static let `default` = MainWindowPrefData(isAllToolsVisible: true,
                                            isToolButtonsVisible: true,
                                            toolPrefDatas: [
                                                ToolPrefData.defaults[.fileBrowser]!,
                                                ToolPrefData.defaults[.bufferList]!,
                                            ])

  var isAllToolsVisible: Bool
  var isToolButtonsVisible: Bool
  var toolPrefDatas: [ToolPrefData]

  init(isAllToolsVisible: Bool, isToolButtonsVisible: Bool, toolPrefDatas: [ToolPrefData]) {
    self.isAllToolsVisible = isAllToolsVisible
    self.isToolButtonsVisible = isToolButtonsVisible
    self.toolPrefDatas = toolPrefDatas
  }

  init?(dict: [String: Any]) {

    guard let isAllToolsVisible = PrefUtils.bool(from: dict, for: MainWindowPrefData.isAllToolsVisible),
          let isToolButtonsVisible = PrefUtils.bool(from: dict, for: MainWindowPrefData.isToolButtonsVisible),
          let toolDataDicts = dict[MainWindowPrefData.toolPrefDatas] as? [[String: Any]]
        else {
      return nil
    }

    // Add default tool pref data for missing identifiers.
    let toolDatas = toolDataDicts.map { ToolPrefData(dict: $0) }.flatMap { $0 }
    let missingToolDatas = Set(ToolIdentifier.all)
        .subtracting(toolDatas.map { $0.identifier })
        .map { ToolPrefData.defaults[$0] }
        .flatMap { $0 }

    self.init(isAllToolsVisible: isAllToolsVisible,
              isToolButtonsVisible: isToolButtonsVisible,
              toolPrefDatas: [ toolDatas, missingToolDatas ].flatMap { $0 })
  }

  func dict() -> [String: Any] {
    return [
      MainWindowPrefData.isAllToolsVisible: self.isAllToolsVisible,
      MainWindowPrefData.isToolButtonsVisible: self.isToolButtonsVisible,
      MainWindowPrefData.toolPrefDatas: self.toolPrefDatas.map { $0.dict() },
    ]
  }

  func toolPrefData(for identifier: ToolIdentifier) -> ToolPrefData {
    guard let tool = self.toolPrefDatas.filter({ $0.identifier == identifier }).first else {
      preconditionFailure("[ERROR] No tool for \(identifier) found!")
    }

    return tool
  }
}

class MainWindowComponent: WindowComponent,
                           NeoVimViewDelegate,
                           NSWindowDelegate,
                           NSUserInterfaceValidations,
                           WorkspaceDelegate
{

  fileprivate static let nibName = "MainWindow"

  fileprivate var defaultEditorFont: NSFont

  fileprivate var _cwd: URL = FileUtils.userHomeUrl

  fileprivate let fontManager = NSFontManager.shared()
  fileprivate let fileItemService: FileItemService

  fileprivate let workspace: Workspace
  fileprivate let neoVimView: NeoVimView
  fileprivate var tools = [ToolIdentifier: WorkspaceToolComponent]()

  // MARK: - API
  var uuid: String {
    return self.neoVimView.uuid
  }

  var cwd: URL {
    get {
      self._cwd = self.neoVimView.cwd
      return self._cwd
    }

    set {
      let oldValue = self._cwd
      if oldValue == newValue {
        return
      }

      self._cwd = newValue
      self.neoVimView.cwd = newValue
      self.fileItemService.unmonitor(url: oldValue)
      self.fileItemService.monitor(url: newValue)
    }
  }

  // TODO: Consider an option object for cwd, urls, etc...
  /**
   The init() method does not show the window. Call MainWindowComponent.show() to do so.
   */
  init(source: Observable<Any>,
       fileItemService: FileItemService,
       cwd: URL,
       urls: [URL] = [],
       initialData: PrefData)
  {
    self.neoVimView = NeoVimView(frame: CGRect.zero,
                                 config: NeoVimView.Config(useInteractiveZsh: initialData.advanced.useInteractiveZsh))
    self.neoVimView.configureForAutoLayout()

    self.workspace = Workspace(mainView: self.neoVimView)

    self.defaultEditorFont = initialData.appearance.editorFont
    self.fileItemService = fileItemService

    super.init(source: source, nibName: MainWindowComponent.nibName)

    self.window.delegate = self
    self.workspace.delegate = self

    self.setupTools(with: initialData.mainWindow)

    self.neoVimView.delegate = self
    self.neoVimView.font = self.defaultEditorFont
    self.neoVimView.usesLigatures = initialData.appearance.editorUsesLigatures
    self.neoVimView.linespacing = initialData.appearance.editorLinespacing
    let neoVimViewCwd = self.neoVimView.cwd
    if neoVimViewCwd == cwd {
      self.fileItemService.monitor(url: cwd)
    } else {
      self.neoVimView.cwd = cwd // The above will publish the MainWindowAction.changeCwd action for the file browser.
    }
    self.neoVimView.open(urls: urls)

    self._cwd = cwd

    self.addReactions()

    self.window.makeFirstResponder(self.neoVimView)
  }

  fileprivate func setupTools(with mainWindowData: MainWindowPrefData) {
    // By default the tool buttons are shown and only the file browser tool is shown.
    let fileBrowserToolData = mainWindowData.toolPrefData(for: .fileBrowser)
    let bufferListToolData = mainWindowData.toolPrefData(for: .bufferList)
{'fraction_non_alphanumeric': 0.08334032731850609, 'fraction_numerical': 0.0015946286193873269, 'mean_word_length': 2.949618826648989, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1856467', 'n_tokens_mistral': 3520, 'n_tokens_neox': 3432, 'n_words': 759}