/ src / gutter-container.js
gutter-container.js
  1  const { Emitter } = require('event-kit');
  2  const Gutter = require('./gutter');
  3  
  4  module.exports = class GutterContainer {
  5    constructor(textEditor) {
  6      this.gutters = [];
  7      this.textEditor = textEditor;
  8      this.emitter = new Emitter();
  9    }
 10  
 11    scheduleComponentUpdate() {
 12      this.textEditor.scheduleComponentUpdate();
 13    }
 14  
 15    destroy() {
 16      // Create a copy, because `Gutter::destroy` removes the gutter from
 17      // GutterContainer's @gutters.
 18      const guttersToDestroy = this.gutters.slice(0);
 19      for (let gutter of guttersToDestroy) {
 20        if (gutter.name !== 'line-number') {
 21          gutter.destroy();
 22        }
 23      }
 24      this.gutters = [];
 25      this.emitter.dispose();
 26    }
 27  
 28    addGutter(options) {
 29      options = options || {};
 30      const gutterName = options.name;
 31      if (gutterName === null) {
 32        throw new Error('A name is required to create a gutter.');
 33      }
 34      if (this.gutterWithName(gutterName)) {
 35        throw new Error(
 36          'Tried to create a gutter with a name that is already in use.'
 37        );
 38      }
 39      const newGutter = new Gutter(this, options);
 40  
 41      let inserted = false;
 42      // Insert the gutter into the gutters array, sorted in ascending order by 'priority'.
 43      // This could be optimized, but there are unlikely to be many gutters.
 44      for (let i = 0; i < this.gutters.length; i++) {
 45        if (this.gutters[i].priority >= newGutter.priority) {
 46          this.gutters.splice(i, 0, newGutter);
 47          inserted = true;
 48          break;
 49        }
 50      }
 51      if (!inserted) {
 52        this.gutters.push(newGutter);
 53      }
 54      this.scheduleComponentUpdate();
 55      this.emitter.emit('did-add-gutter', newGutter);
 56      return newGutter;
 57    }
 58  
 59    getGutters() {
 60      return this.gutters.slice();
 61    }
 62  
 63    gutterWithName(name) {
 64      for (let gutter of this.gutters) {
 65        if (gutter.name === name) {
 66          return gutter;
 67        }
 68      }
 69      return null;
 70    }
 71  
 72    observeGutters(callback) {
 73      for (let gutter of this.getGutters()) {
 74        callback(gutter);
 75      }
 76      return this.onDidAddGutter(callback);
 77    }
 78  
 79    onDidAddGutter(callback) {
 80      return this.emitter.on('did-add-gutter', callback);
 81    }
 82  
 83    onDidRemoveGutter(callback) {
 84      return this.emitter.on('did-remove-gutter', callback);
 85    }
 86  
 87    /*
 88    Section: Private Methods
 89    */
 90  
 91    // Processes the destruction of the gutter. Throws an error if this gutter is
 92    // not within this gutterContainer.
 93    removeGutter(gutter) {
 94      const index = this.gutters.indexOf(gutter);
 95      if (index > -1) {
 96        this.gutters.splice(index, 1);
 97        this.scheduleComponentUpdate();
 98        this.emitter.emit('did-remove-gutter', gutter.name);
 99      } else {
100        throw new Error(
101          'The given gutter cannot be removed because it is not ' +
102            'within this GutterContainer.'
103        );
104      }
105    }
106  
107    // The public interface is Gutter::decorateMarker or TextEditor::decorateMarker.
108    addGutterDecoration(gutter, marker, options) {
109      if (gutter.type === 'line-number') {
110        options.type = 'line-number';
111      } else {
112        options.type = 'gutter';
113      }
114      options.gutterName = gutter.name;
115      return this.textEditor.decorateMarker(marker, options);
116    }
117  };