<commit_before>/* @flow */

import { CompositeDisposable, Emitter } from 'sb-event-kit'
import debounce from 'sb-debounce'
import disposableEvent from 'disposable-event'
import { calculateDecorations } from './helpers'
import type { LinterMessage, MessagesPatch, TreeViewHighlight } from '../types'

export default class TreeView {
  element: HTMLElement;
  emitter: Emitter;
  messages: Array<LinterMessage>;
  decorations: Object;
  subscriptions: CompositeDisposable;
  decorateOnTreeView: 'Files and Directories' | 'Files' | 'None';

  constructor() {
    this.element = TreeView.getElement()
    this.emitter = new Emitter()
    this.messages = []
    this.decorations = {}
    this.subscriptions = new CompositeDisposable()

    this.subscriptions.add(this.emitter)
    this.subscriptions.add(atom.config.observe('linter-ui-default.decorateOnTreeView', decorateOnTreeView => {
      if (typeof this.decorateOnTreeView === 'undefined') {
        this.decorateOnTreeView = decorateOnTreeView
      } else if (decorateOnTreeView === 'None') {
        this.render()
        this.decorateOnTreeView = decorateOnTreeView
      } else {
        const messages = this.messages
        this.render()
        this.decorateOnTreeView = decorateOnTreeView
        this.render(messages)
      }
    }))

    if (this.element) {
      this.subscriptions.add(disposableEvent(this.element, 'click', debounce(() => {
        this.render(this.messages)
      })))
    }
  }
  apply(difference: MessagesPatch) {
    this.messages = difference.messages
    const element = TreeView.getElement()
    const decorateOnTreeView = this.decorateOnTreeView
    if (!element || decorateOnTreeView === 'None') {
      return
    }

    this.applyDecorations(calculateDecorations(decorateOnTreeView, difference.messages))
  }
  render(messages: Array<LinterMessage> = []) {
    this.apply({ added: [], messages: [], removed: this.messages })
    if (messages.length) {
      this.apply({ added: messages, messages, removed: [] })
    }
  }
  applyDecorations(decorations: Object) {
    if (!this.element) {
      return
    }
    for (const filePath in this.decorations) {
      if (!{}.hasOwnProperty.call(this.decorations, filePath)) {
        continue
      }
      if (!decorations[filePath]) {
        // Removed
        const element = TreeView.getElementByPath(this.element, filePath)
        if (element) {
          this.removeDecoration(element)
        }
      }
    }
    for (const filePath in decorations) {
      if (!{}.hasOwnProperty.call(decorations, filePath)) {
        continue
      }
      const element = TreeView.getElementByPath(this.element, filePath)
      if (!element) {
        continue
      }
      this.handleDecoration(element, this.decorations[filePath], decorations[filePath])
    }
    this.decorations = decorations
  }
  handleDecoration(element: HTMLElement, update: boolean = false, highlights: TreeViewHighlight) {
    let decoration
    if (update) {
      decoration = element.querySelector('linter-decoration')
    }
    if (decoration) {
      decoration.className = ''
    } else {
      decoration = document.createElement('linter-decoration')
      element.appendChild(decoration)
    }
    if (highlights.error) {
      decoration.classList.add('linter-error')
    } else if (highlights.warning) {
      decoration.classList.add('linter-warning')
    } else if (highlights.info) {
      decoration.classList.add('linter-info')
    }
  }
  removeDecoration(element: HTMLElement) {
    const decoration = element.querySelector('linter-decoration')<commit_msg>:bug: Fix ocasional breakage of tree view highlights caused by a race condition between core and community packages<commit_after>/* @flow */

import { CompositeDisposable, Emitter } from 'sb-event-kit'
import debounce from 'sb-debounce'
import disposableEvent from 'disposable-event'
import { calculateDecorations } from './helpers'
import type { LinterMessage, MessagesPatch, TreeViewHighlight } from '../types'

export default class TreeView {
  emitter: Emitter;
  messages: Array<LinterMessage>;
  decorations: Object;
  subscriptions: CompositeDisposable;
  decorateOnTreeView: 'Files and Directories' | 'Files' | 'None';

  constructor() {
    this.emitter = new Emitter()
    this.messages = []
    this.decorations = {}
    this.subscriptions = new CompositeDisposable()

    this.subscriptions.add(this.emitter)
    this.subscriptions.add(atom.config.observe('linter-ui-default.decorateOnTreeView', decorateOnTreeView => {
      if (typeof this.decorateOnTreeView === 'undefined') {
        this.decorateOnTreeView = decorateOnTreeView
      } else if (decorateOnTreeView === 'None') {
        this.render()
        this.decorateOnTreeView = decorateOnTreeView
      } else {
        const messages = this.messages
        this.render()
        this.decorateOnTreeView = decorateOnTreeView
        this.render(messages)
      }
    }))

    setTimeout(() => {
      const element = TreeView.getElement()
      if (this.subscriptions.disposed || !element) {
        return
      }
      this.subscriptions.add(disposableEvent(element, 'click', debounce(() => {
        this.render(this.messages)
      })))
    }, 1000)
  }
  apply(difference: MessagesPatch) {
    this.messages = difference.messages
    const element = TreeView.getElement()
    const decorateOnTreeView = this.decorateOnTreeView
    if (!element || decorateOnTreeView === 'None') {
      return
    }

    this.applyDecorations(calculateDecorations(decorateOnTreeView, difference.messages))
  }
  render(messages: Array<LinterMessage> = []) {
    this.apply({ added: [], messages: [], removed: this.messages })
    if (messages.length) {
      this.apply({ added: messages, messages, removed: [] })
    }
  }
  applyDecorations(decorations: Object) {
    const treeViewElement = TreeView.getElement()
    if (!treeViewElement) {
      return
    }
    for (const filePath in this.decorations) {
      if (!{}.hasOwnProperty.call(this.decorations, filePath)) {
        continue
      }
      if (!decorations[filePath]) {
        // Removed
        const element = TreeView.getElementByPath(treeViewElement, filePath)
        if (element) {
          this.removeDecoration(element)
        }
      }
    }
    for (const filePath in decorations) {
      if (!{}.hasOwnProperty.call(decorations, filePath)) {
        continue
      }
      const element = TreeView.getElementByPath(treeViewElement, filePath)
      if (!element) {
        continue
      }
      this.handleDecoration(element, this.decorations[filePath], decorations[filePath])
    }
    this.decorations = decorations
  }
  handleDecoration(element: HTMLElement, update: boolean = false, highlights: TreeViewHighlight) {
    let decoration
    if (update) {
      decoration = element.querySelector('linter-decoration')
    }
    if (decoration) {
      decoration.className = ''
    } else {
      decoration = document.createElement('linter-decoration')
      element.appendChild(decoration)
    }
    if (highlights.error) {
      decoration.classList.add('linter-error')
    } else if (highlights.warning) {
      decoration.classList.add('linter-warning')
    } else if (highlights.info) {
      decoration.classList.add('linter-info')
    }
  }
  removeDecoration(element: HTMLElement) {
    const decoration = element.querySelector('linter-decoration')
{'fraction_non_alphanumeric': 0.11672308733920109, 'fraction_numerical': 0.0005416384563303995, 'mean_word_length': 3.1634723788049603, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '775168', 'n_tokens_mistral': 2176, 'n_tokens_neox': 2119, 'n_words': 420}