<commit_before>'use babel'

import {TextEditor, Emitter, CompositeDisposable} from 'atom'

export default class EditorLinter {
  constructor(editor) {
    if (!(editor instanceof TextEditor)) {
      throw new Error('Given editor is not really an editor')
    }

    this.editor = editor
    this.emitter = new Emitter()
    this.markers = new WeakMap()
    this.subscriptions = new CompositeDisposable

    this.subscriptions.add(atom.config.observe('linter.underlineIssues', underlineIssues =>
      this.underlineIssues = underlineIssues
    ))
    this.subscriptions.add(this.editor.onDidDestroy(() =>
      this.destroy()
    ))
    this.subscriptions.add(this.editor.onDidSave(() =>
      this.emitter.emit('should-lint', false)
    ))
    this.subscriptions.add(this.editor.onDidChangeCursorPosition(({oldBufferPosition, newBufferPosition}) => {
      if (newBufferPosition.row !== oldBufferPosition.row) {
        this.emitter.emit('should-update-line-messages')
      }
      this.emitter.emit('should-update-bubble')
    }))

    // Atom invokes the onDidStopChanging callback immediately on creation. So we wait a moment
    setImmediate(() => {
      this.subscriptions.add(this.editor.onDidStopChanging(() =>
        this.emitter.emit('should-lint', true)
      ))
    })
  }

  addMessage(message) {
    const marker = this.editor.markBufferRange(message.range, {invalidate: 'inside'})
    this.markers.set(message, marker)
    this.editor.decorateMarker(marker, {
      type: 'line-number',
      class: `linter-highlight ${message.class}`
    })
    if (this.underlineIssues) {
      this.editor.decorateMarker(marker, {
        type: 'highlight',
        class: `linter-highlight ${message.class}`
      })
    }
  }

  removeMessage(message) {
    if (this.markers.has(message)) {
      const marker = this.markers.get(message)
      marker.destroy()
      this.markers.delete(marker)
    }
  }

  lint(onChange = false) {
    this.emitter.emit('should-lint', onChange)
  }

  onShouldUpdateBubble(callback) {
    return this.emitter.on('should-update-bubble', callback)
  }

  onShouldUpdateLineMessages(callback) {
    return this.emitter.on('should-update-line-messages', callback)
  }

  onShouldLint(callback) {
    return this.emitter.on('should-lint', callback)
  }

  onDidDestroy(callback) {
    return this.emitter.on('did-destroy', callback)
  }

  destroy() {
    this.emitter.emit('did-destroy')
    this.dispose()
  }

  dispose() {
    this.subscriptions.dispose()
  }
}<commit_msg>:new: Add basic gutter APIs to editor-linter<commit_after>'use babel'

import {TextEditor, Emitter, CompositeDisposable} from 'atom'

export default class EditorLinter {
  constructor(editor) {
    if (!(editor instanceof TextEditor)) {
      throw new Error('Given editor is not really an editor')
    }

    this.editor = editor
    this.emitter = new Emitter()
    this.markers = new Map()
    this.gutter = null
    this.subscriptions = new CompositeDisposable

    this.subscriptions.add(atom.config.observe('linter.underlineIssues', underlineIssues =>
      this.underlineIssues = underlineIssues
    ))
    this.subscriptions.add(this.editor.onDidDestroy(() =>
      this.destroy()
    ))
    this.subscriptions.add(this.editor.onDidSave(() =>
      this.emitter.emit('should-lint', false)
    ))
    this.subscriptions.add(this.editor.onDidChangeCursorPosition(({oldBufferPosition, newBufferPosition}) => {
      if (newBufferPosition.row !== oldBufferPosition.row) {
        this.emitter.emit('should-update-line-messages')
      }
      this.emitter.emit('should-update-bubble')
    }))
    this.subscriptions.add(atom.config.observe('linter.gutterEnabled', () =>
      this.handleGutter()
    ))
    // Using onDidChange here 'cause the same function is invoked above
    this.subscriptions.add(atom.config.onDidChange('linter.gutterPosition', () =>
      this.handleGutter()
    ))

    // Atom invokes the onDidStopChanging callback immediately on creation. So we wait a moment
    setImmediate(() => {
      this.subscriptions.add(this.editor.onDidStopChanging(() =>
        this.emitter.emit('should-lint', true)
      ))
    })
  }

  handleGutter() {
    const status = atom.config.get('linter.gutterEnabled')

    if (this.gutter !== null) this.removeGutter()
    if (status) this.addGutter()
  }

  addGutter() {
    // TODO: Add existing messages
    // TODO: Make the position work
    const position = atom.config.get('linter.gutterPosition')
    this.gutter = this.editor.addGutter({
      name: 'linter',
      priority: 100
    })
  }

  removeGutter() {
    if (this.gutter !== null) {
      this.gutter.destroy()
      this.gutter = null
    }
  }

  addMessage(message) {
    const marker = this.editor.markBufferRange(message.range, {invalidate: 'inside'})
    this.markers.set(message, marker)
    this.editor.decorateMarker(marker, {
      type: 'line-number',
      class: `linter-highlight ${message.class}`
    })
    if (this.underlineIssues) {
      this.editor.decorateMarker(marker, {
        type: 'highlight',
        class: `linter-highlight ${message.class}`
      })
    }
  }

  removeMessage(message) {
    if (this.markers.has(message)) {
      const marker = this.markers.get(message)
      marker.destroy()
      this.markers.delete(marker)
    }
  }

  lint(onChange = false) {
    this.emitter.emit('should-lint', onChange)
  }

  onShouldUpdateBubble(callback) {
    return this.emitter.on('should-update-bubble', callback)
  }

  onShouldUpdateLineMessages(callback) {
    return this.emitter.on('should-update-line-messages', callback)
  }

  onShouldLint(callback) {
    return this.emitter.on('should-lint', callback)
  }

  onDidDestroy(callback) {
    return this.emitter.on('did-destroy', callback)
  }

  destroy() {
    this.emitter.emit('did-destroy')
    this.dispose()
  }

  dispose() {
    if (this.markers.size) {
      this.markers.forEach(marker => marker.destroy())
    }
    this.removeGutter()
    this.subscriptions.dispose()
  }
}
{'fraction_non_alphanumeric': 0.13609172482552342, 'fraction_numerical': 0.0004985044865403788, 'mean_word_length': 3.159640635798203, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 18, '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': '5152508', 'n_tokens_mistral': 1939, 'n_tokens_neox': 1865, 'n_words': 314}