Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

APIs for manipulating gutters #12742

Merged
merged 10 commits into from
Feb 13, 2017
Merged

Conversation

thehogfather
Copy link
Contributor

@thehogfather thehogfather commented Aug 31, 2016

(Review and test) @zaggino @marcelgerber @petetnt

This introduces 5 public apis on the Editor instance for manipulating gutters in an editor.
The intended workflow is that extension authors use this api to manipulate the gutter within the editor. Typically onActiveEditorChange, register the gutter if not already registered. Use unregisterGutter if the extension is disabled and use setGutterMarker to draw things on the gutter.

None of the apis could go on the EditorManager since EditorManager already depends on Editor.js and we would have needed to make Editor depend on EditorManager to be aware of registered gutters when clearing gutters or setting the gutter markers.

As it stands, this will not play nicely with the current ways extensions are written since they are not been registered first and there is no clean way to allow backward compatibility for the old method of manipulation of gutters. Things appear to work correctly, until the line-number visibility is toggled (then external extension gutter disappears until it is re-rendered).

@zaggino it would be nice to know how well this plays with your extensions. I've only been able to test with two things on the gutter.

    /**
     * Clears all marks from the gutter with the specified name.
     * @param {string} name The name of the gutter to remove.
     */
    Editor.prototype.clearGutter = function (name);

    /**
     * Sets the marker for the specified gutter on the specified line number
     * @param   {string}   lineNumber The line number for the inserted gutter marker
     * @param   {string}   gutterName The name of the gutter
     * @param   {object}   marker     The jquery object representint the marker to the inserted in the gutter
     */
    Editor.prototype.setGutterMarker = function (lineNumber, gutterName, marker);

    /**
     * Returns the list of gutters current registered on this editor.
     * @return {!Array.<{name: string, priority: number}>}
     */
    Editor.getRegisteredGutters = function ();

    /**
     * Registers the gutter with the specified name at the given priority.
     * @param {string} name    The name to register with the quarter
     * @param {number} priority  A number denoting the priority of the gutter. Priorities higher than 100 appear after the line numbers. Priority less than 100 appear before.
     * @param {?Array<number>} languageIds A list of language ids that this gutter is valid for. If no language ids are passed, then the gutter is valid in all languages.
     */
    Editor.registerGutter = function (name, priority, languageIds);

    /**
     * Unregisters the gutter with the specified name and removes it from the UI.
     * @param {string} name The name of the gutter to be un registered.
     */
    Editor.unregisterGutter = function (name);

relates to #12726

@@ -2330,6 +2341,11 @@ define(function (require, exports, module) {
} else if (prefName === SHOW_LINE_NUMBERS) {
Editor._toggleLinePadding(!newValue);
this._codeMirror.setOption(cmOptions[SHOW_LINE_NUMBERS], newValue);
if (newValue) {
this.registerGutter(LINE_NUMBER_GUTTER, 100);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magic number 100, make a constant please (probably exportable)

@zaggino
Copy link
Contributor

zaggino commented Aug 31, 2016

Initial review, it's be worth exploring if registerGutter, unregisterGutter & getRegisteredGutters would not be better placed on Editor instead of Editor.prototype OR removing the language parameters and related code.

@zaggino zaggino added this to the Release 1.8 milestone Aug 31, 2016
@ficristo
Copy link
Collaborator

ficristo commented Sep 1, 2016

Just some thoughts.
Editor is becoming a bit big: is it worth to put this on a new file?
Can we enforce the codefolding arrows to always be the rightmost thing in the gutter?

registering/unregistering/getting registered gutters are now `static` objects on the editor class.
modified code folding gutter to use new api.
@thehogfather
Copy link
Contributor Author

@ficristo I could explore the consequences of a refactor which introduces a Gutter abstraction for manipulating gutter elements. Hypothetically, Gutter instances would be composed as properties of Editor instances. Registering/unregistering/listing gutters would be static properties of the Gutter class. The Gutter class would also need a reference to the Editor instance (since any modifications to the gutter are still done via the _codeMirror editor property). This could probably be spiked in more detail in a separate issue. But there is usually a tradeoff in a refactor such as this. Javascript is not strongly typed so it is would become more difficult to navigate the project structure, while still having this "central" dependency (i.e., editor._codeMirror) that more and more files get access to.

I'm not 100% in agreement with enforcing that the code-folding arrows are the rightmost in the gutter. I think we should provide a flexible API with as little exceptions and restrictions as possible. For instance a very creative designer might conjure up a wireframe where a very thin gutter element is used to render the range (i.e., start and end) that a foldable region spans. I'm sure people can come up with other examples. For now I have given the priority what I feel is a sufficiently high number (500) to push the folding gutter to the rightmost gutter. I think this is fine as long as we make the gutter priorities and how they work clear to the extension developers.

I am yet to write tests for these and I will as soon as I can get the tests to run. Anyone else having issues running tests? All I see is a white screen when I try. Any known workarounds?

@ficristo
Copy link
Collaborator

ficristo commented Sep 3, 2016

For the first point I was looking both at the length of Editor and what is happening in CodeMirror where it is split in multiple files. So I was thinking to put the gutter in a new file with the idea to put/move more things in the future.
For the second I thought the core extensions should be have the most priority. But you are right it would limit extension author.
Thank you for the explanations, if necessary we can think more about these things in future releases.

For the tests I don't have problems on my Windows10 PC. Are you saying as soon as you open the test window you see only a white screen?

@thehogfather
Copy link
Contributor Author

@ficristo regarding tests, yes. That is correct. I only see a white screen as soon as the test windows are opened. I am on OSX El Capitan. I was just wondering if anyone has ever experienced this issue and if there were known work arounds.

@ficristo ficristo modified the milestones: Release 1.9, Release 1.8 Sep 5, 2016
@zaggino
Copy link
Contributor

zaggino commented Sep 29, 2016

@thehogfather can you run tests now?

@thehogfather
Copy link
Contributor Author

@zaggino yes i can now run tests (at least selective tests) attempting to run all tests eventually gets to a white screen but I think this has always been a problem for me - even from a couple of years ago. Let me know if there are any strategies/workarounds for running all tests.

@zaggino
Copy link
Contributor

zaggino commented Sep 29, 2016

Yeah, I don't know of any. If you build your own shell, try using the release one from pre-release. If you don't try building your own. That's pretty much all advice I have (which I know it's not a lot).

@thehogfather
Copy link
Contributor Author

ok - building my own shell did not make a huge difference with regards to running All tests. Presumably someone is able to. I've updated the PR with additional tests to Editor-tests to cover the gutter api functionality.

@zaggino
Copy link
Contributor

zaggino commented Oct 13, 2016

@ficristo second look?

@ficristo
Copy link
Collaborator

If someone else of @adobe/brackets-committers can review would be better, maybe @petetnt or @marcelgerber.
@zaggino my only question, more for curiosity than anything else, have you tryed it with one of your extensions?

@swmitra
Copy link
Collaborator

swmitra commented Oct 13, 2016

As a whole the idea of adding gutter manipulation using well defined public apis sounds quite promising.
The only obvious issue what I see ( an existing one ) is the CM native "lineNumber" gutter hide and show which would shuffle the gutter sequence. To resolve that issue I would like to have one more option as an abstracted logic from the extensions, where we have a gutter management module that registers a DOM mutation listener ( preferably a MutationObserver setup on the CM gutter wrapper ) , that would enforce the gutter markers sequence when required.

I am not quite clear about the usage of language ID though as a parameter.

@zaggino
Copy link
Contributor

zaggino commented Oct 13, 2016

@ficristo no, I haven't tried modifying any of my extensions to use the API yet

@ficristo
Copy link
Collaborator

@swmitra initially there was this #12673 which used MutationObserver. Since it caused some problems this was opened.
@thehogfather can comment more.

@thehogfather
Copy link
Contributor Author

@swmitra @ficristo Indeed one of the issues (#10864) that triggered this was discovered as a 'Codefolding' issue. Consequently, the original fix involved using the MutationObserver in the code-folding module to listen to changes to the gutter (e.g., when line numbers are hidden/shown) and making sure the codefolding gutter is always inserted after that. The fix was shortsighted and did not fix the underlying lack of API. Moreover, it also gave rise to at least one bug with scroll states of the editor - see #12726.

With the new API, I don't think there is need to watch changes to the gutter - to detect if lineNumbers are present or not. Brackets already watches changes to the preferences (one of which is whether or not to show line numbers) and the new API uses this information to ensure that the state of the gutters rendered in the editor is consistent with that of the external lineNumber preferences -- see the diffs in Editor._updateOption and Editor._renderGutters (comments on those would be particularly welcome). There is also the notion of gutter priority, which is used to sort and render the gutters in a consistent order - even when they are unregistered and then later re-registered. This should prevent any shuffling that might potentially occur. I have fixed the priority of the lineNumber gutter at an arbitrary value of 100. Gutters with less priority appear to the left and higher priorities appear to the right of the line numbers. Gutters with equal priorities are rendered in the order they were registered. When a gutter is shown/hidden, it is simply added/removed from the registered gutters list with the requisite priority value.

Perhaps one would want these level api changes in CodeMirror itself as it does not provide an API rich enough for these use cases?

Finally, the usage of languageId is to allow flexibility in specifying that an extension developer would like their gutter design only for specific languages. For instance, I remember there is/was a nice extension that showed a colored square in the gutter of stylesheet files right next to lines where colors have been used. Such a gutter need not exist in javascript files. So on initialisation, the extension editor could then register gutter for specific languages. Default is all languages.


/**
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: indent.

* The line number gutter is defined as { name: LINE_NUMBER_GUTTER, priority: 100 }
* @type {Array.<{name: string, priority: number, languages: Array}}
*/
var registeredGutters = [];
var cmOptions = {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a new line before cmOptions.

@@ -110,7 +115,8 @@ define(function (require, exports, module) {
DEFAULT_SPACE_UNITS = 4,
DEFAULT_TAB_SIZE = 4,
MAX_SPACE_UNITS = 10,
MAX_TAB_SIZE = 10;
MAX_TAB_SIZE = 10,
LINE_NUMBER_GUTTER_PRIORITY = 100;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you instead create a new var group here with LINE_NUMBER_GUTTER?

var LINE_NUMBER_GUTTER           = "CodeMirror-linenumbers",
    LINE_NUMBER_GUTTER_PRIORITY  = 100;

@@ -414,13 +421,16 @@ define(function (require, exports, module) {
readOnly : isReadOnly
});


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert

@@ -449,6 +459,7 @@ define(function (require, exports, module) {
this._resetText(document.getText());
this._duringSync = false;


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert

}

if (!name || typeof name !== "string") {
throw new Error("The name of the registered gutter must be a string.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure: @zaggino do we throw exceptions on wrong parameters?
Maybe a console.error followed by a return is better?

@@ -73,6 +75,7 @@ define(function (require, exports, module) {

/** Set to true when init() has run; set back to false after deinit() has run */
var _isInitialized = false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a new line before initialisedFiles. Can you add a comment too?

@@ -383,6 +395,7 @@ define(function (require, exports, module) {
foldCode.init();
foldGutter.init();


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert


// If the line numbers gutter has not been explicitly registered and the CodeMirror lineNumbes option is
// set to true, we explicitly add the line numbers gutter. This case occurs the first time the editor loads
// and showwLineNumbers is set to true in preferences
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: showLineNumbers, only one w

rootElement = this.getRootElement();

// If the line numbers gutter has not been explicitly registered and the CodeMirror lineNumbes option is
// set to true, we explicitly add the line numbers gutter. This case occurs the first time the editor loads
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: add the line numbers gutter, a single space between words.

@ficristo
Copy link
Collaborator

I left only nits, from the API perspective I cannot really say much.

A general rule: do not touch whitespaces on code you don't touch.

Maybe, as already said by @zaggino, we should exports LINE_NUMBER_GUTTER_PRIORITY and CODE_FOLDING_GUTTER_PRIORITY.
Worth adding a test that manipulate the gutter after the code folding markers?

@thehogfather if we can upstream some changes to CodeMirror, to me would be really nice.

* @param {string} name The name of the gutter to be un registered.
*/
Editor.unregisterGutter = function (name) {
var i, gutter;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe would prefer Array.prototype.filter for simplicity over couple of possible cycles saved 🤔 😄


/**
* Unregisters the gutter with the specified name and removes it from the UI.
* @param {string} name The name of the gutter to be un registered.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: "unregistered"

@swmitra swmitra self-requested a review February 10, 2017 07:53
Copy link
Collaborator

@swmitra swmitra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me and ready to be merged.

@ficristo
Copy link
Collaborator

@petetnt do you want to review? Otherwise I'll merge this.

@petetnt
Copy link
Collaborator

petetnt commented Feb 13, 2017

LGTM too, merging this.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants