mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
``` npm install -g jscodeshift git clone https://github.com/cpojer/js-codemod.git jscodeshift -t js-codemod/transforms/no-vars.js ./src ```
288 lines
8.8 KiB
JavaScript
288 lines
8.8 KiB
JavaScript
const Grim = require('grim');
|
|
const { Disposable } = require('event-kit');
|
|
|
|
const AnyConstructor = Symbol('any-constructor');
|
|
|
|
// Essential: `ViewRegistry` handles the association between model and view
|
|
// types in Atom. We call this association a View Provider. As in, for a given
|
|
// model, this class can provide a view via {::getView}, as long as the
|
|
// model/view association was registered via {::addViewProvider}
|
|
//
|
|
// If you're adding your own kind of pane item, a good strategy for all but the
|
|
// simplest items is to separate the model and the view. The model handles
|
|
// application logic and is the primary point of API interaction. The view
|
|
// just handles presentation.
|
|
//
|
|
// Note: Models can be any object, but must implement a `getTitle()` function
|
|
// if they are to be displayed in a {Pane}
|
|
//
|
|
// View providers inform the workspace how your model objects should be
|
|
// presented in the DOM. A view provider must always return a DOM node, which
|
|
// makes [HTML 5 custom elements](http://www.html5rocks.com/en/tutorials/webcomponents/customelements/)
|
|
// an ideal tool for implementing views in Atom.
|
|
//
|
|
// You can access the `ViewRegistry` object via `atom.views`.
|
|
module.exports = class ViewRegistry {
|
|
constructor(atomEnvironment) {
|
|
this.animationFrameRequest = null;
|
|
this.documentReadInProgress = false;
|
|
this.performDocumentUpdate = this.performDocumentUpdate.bind(this);
|
|
this.atomEnvironment = atomEnvironment;
|
|
this.clear();
|
|
}
|
|
|
|
clear() {
|
|
this.views = new WeakMap();
|
|
this.providers = [];
|
|
this.clearDocumentRequests();
|
|
}
|
|
|
|
// Essential: Add a provider that will be used to construct views in the
|
|
// workspace's view layer based on model objects in its model layer.
|
|
//
|
|
// ## Examples
|
|
//
|
|
// Text editors are divided into a model and a view layer, so when you interact
|
|
// with methods like `atom.workspace.getActiveTextEditor()` you're only going
|
|
// to get the model object. We display text editors on screen by teaching the
|
|
// workspace what view constructor it should use to represent them:
|
|
//
|
|
// ```coffee
|
|
// atom.views.addViewProvider TextEditor, (textEditor) ->
|
|
// textEditorElement = new TextEditorElement
|
|
// textEditorElement.initialize(textEditor)
|
|
// textEditorElement
|
|
// ```
|
|
//
|
|
// * `modelConstructor` (optional) Constructor {Function} for your model. If
|
|
// a constructor is given, the `createView` function will only be used
|
|
// for model objects inheriting from that constructor. Otherwise, it will
|
|
// will be called for any object.
|
|
// * `createView` Factory {Function} that is passed an instance of your model
|
|
// and must return a subclass of `HTMLElement` or `undefined`. If it returns
|
|
// `undefined`, then the registry will continue to search for other view
|
|
// providers.
|
|
//
|
|
// Returns a {Disposable} on which `.dispose()` can be called to remove the
|
|
// added provider.
|
|
addViewProvider(modelConstructor, createView) {
|
|
let provider;
|
|
if (arguments.length === 1) {
|
|
switch (typeof modelConstructor) {
|
|
case 'function':
|
|
provider = {
|
|
createView: modelConstructor,
|
|
modelConstructor: AnyConstructor
|
|
};
|
|
break;
|
|
case 'object':
|
|
Grim.deprecate(
|
|
'atom.views.addViewProvider now takes 2 arguments: a model constructor and a createView function. See docs for details.'
|
|
);
|
|
provider = modelConstructor;
|
|
break;
|
|
default:
|
|
throw new TypeError('Arguments to addViewProvider must be functions');
|
|
}
|
|
} else {
|
|
provider = { modelConstructor, createView };
|
|
}
|
|
|
|
this.providers.push(provider);
|
|
return new Disposable(() => {
|
|
this.providers = this.providers.filter(p => p !== provider);
|
|
});
|
|
}
|
|
|
|
getViewProviderCount() {
|
|
return this.providers.length;
|
|
}
|
|
|
|
// Essential: Get the view associated with an object in the workspace.
|
|
//
|
|
// If you're just *using* the workspace, you shouldn't need to access the view
|
|
// layer, but view layer access may be necessary if you want to perform DOM
|
|
// manipulation that isn't supported via the model API.
|
|
//
|
|
// ## View Resolution Algorithm
|
|
//
|
|
// The view associated with the object is resolved using the following
|
|
// sequence
|
|
//
|
|
// 1. Is the object an instance of `HTMLElement`? If true, return the object.
|
|
// 2. Does the object have a method named `getElement` that returns an
|
|
// instance of `HTMLElement`? If true, return that value.
|
|
// 3. Does the object have a property named `element` with a value which is
|
|
// an instance of `HTMLElement`? If true, return the property value.
|
|
// 4. Is the object a jQuery object, indicated by the presence of a `jquery`
|
|
// property? If true, return the root DOM element (i.e. `object[0]`).
|
|
// 5. Has a view provider been registered for the object? If true, use the
|
|
// provider to create a view associated with the object, and return the
|
|
// view.
|
|
//
|
|
// If no associated view is returned by the sequence an error is thrown.
|
|
//
|
|
// Returns a DOM element.
|
|
getView(object) {
|
|
if (object == null) {
|
|
return;
|
|
}
|
|
|
|
let view = this.views.get(object);
|
|
if (!view) {
|
|
view = this.createView(object);
|
|
this.views.set(object, view);
|
|
}
|
|
return view;
|
|
}
|
|
|
|
createView(object) {
|
|
if (object instanceof HTMLElement) {
|
|
return object;
|
|
}
|
|
|
|
let element;
|
|
if (object && typeof object.getElement === 'function') {
|
|
element = object.getElement();
|
|
if (element instanceof HTMLElement) {
|
|
return element;
|
|
}
|
|
}
|
|
|
|
if (object && object.element instanceof HTMLElement) {
|
|
return object.element;
|
|
}
|
|
|
|
if (object && object.jquery) {
|
|
return object[0];
|
|
}
|
|
|
|
for (let provider of this.providers) {
|
|
if (provider.modelConstructor === AnyConstructor) {
|
|
element = provider.createView(object, this.atomEnvironment);
|
|
if (element) {
|
|
return element;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (object instanceof provider.modelConstructor) {
|
|
element =
|
|
provider.createView &&
|
|
provider.createView(object, this.atomEnvironment);
|
|
if (element) {
|
|
return element;
|
|
}
|
|
|
|
let ViewConstructor = provider.viewConstructor;
|
|
if (ViewConstructor) {
|
|
element = new ViewConstructor();
|
|
if (element.initialize) {
|
|
element.initialize(object);
|
|
} else if (element.setModel) {
|
|
element.setModel(object);
|
|
}
|
|
return element;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (object && object.getViewClass) {
|
|
let ViewConstructor = object.getViewClass();
|
|
if (ViewConstructor) {
|
|
const view = new ViewConstructor(object);
|
|
return view[0];
|
|
}
|
|
}
|
|
|
|
throw new Error(
|
|
`Can't create a view for ${
|
|
object.constructor.name
|
|
} instance. Please register a view provider.`
|
|
);
|
|
}
|
|
|
|
updateDocument(fn) {
|
|
this.documentWriters.push(fn);
|
|
if (!this.documentReadInProgress) {
|
|
this.requestDocumentUpdate();
|
|
}
|
|
return new Disposable(() => {
|
|
this.documentWriters = this.documentWriters.filter(
|
|
writer => writer !== fn
|
|
);
|
|
});
|
|
}
|
|
|
|
readDocument(fn) {
|
|
this.documentReaders.push(fn);
|
|
this.requestDocumentUpdate();
|
|
return new Disposable(() => {
|
|
this.documentReaders = this.documentReaders.filter(
|
|
reader => reader !== fn
|
|
);
|
|
});
|
|
}
|
|
|
|
getNextUpdatePromise() {
|
|
if (this.nextUpdatePromise == null) {
|
|
this.nextUpdatePromise = new Promise(resolve => {
|
|
this.resolveNextUpdatePromise = resolve;
|
|
});
|
|
}
|
|
|
|
return this.nextUpdatePromise;
|
|
}
|
|
|
|
clearDocumentRequests() {
|
|
this.documentReaders = [];
|
|
this.documentWriters = [];
|
|
this.nextUpdatePromise = null;
|
|
this.resolveNextUpdatePromise = null;
|
|
if (this.animationFrameRequest != null) {
|
|
cancelAnimationFrame(this.animationFrameRequest);
|
|
this.animationFrameRequest = null;
|
|
}
|
|
}
|
|
|
|
requestDocumentUpdate() {
|
|
if (this.animationFrameRequest == null) {
|
|
this.animationFrameRequest = requestAnimationFrame(
|
|
this.performDocumentUpdate
|
|
);
|
|
}
|
|
}
|
|
|
|
performDocumentUpdate() {
|
|
const { resolveNextUpdatePromise } = this;
|
|
this.animationFrameRequest = null;
|
|
this.nextUpdatePromise = null;
|
|
this.resolveNextUpdatePromise = null;
|
|
|
|
let writer = this.documentWriters.shift();
|
|
while (writer) {
|
|
writer();
|
|
writer = this.documentWriters.shift();
|
|
}
|
|
|
|
let reader = this.documentReaders.shift();
|
|
this.documentReadInProgress = true;
|
|
while (reader) {
|
|
reader();
|
|
reader = this.documentReaders.shift();
|
|
}
|
|
this.documentReadInProgress = false;
|
|
|
|
// process updates requested as a result of reads
|
|
writer = this.documentWriters.shift();
|
|
while (writer) {
|
|
writer();
|
|
writer = this.documentWriters.shift();
|
|
}
|
|
|
|
if (resolveNextUpdatePromise) {
|
|
resolveNextUpdatePromise();
|
|
}
|
|
}
|
|
};
|