+ RESIZE_BRIEF: 1 << 0,
+
+ WAITING_MESSAGE: _("completion.generating"),
+
+ Group: Class("ItemList.Group", {
+ init: function init(parent, context) {
+ this.parent = parent;
+ this.context = context;
+ this.offsets = {};
+ this.range = ItemList.Range(0, 0);
+ },
+
+ get rootXML()
+ <div key="root" highlight="CompGroup">
+ <div highlight="Completions">
+ { this.context.createRow(this.context.title || [], "CompTitle") }
+ </div>
+ <div highlight="CompTitleSep"/>
+ <div key="contents">
+ <div key="up" highlight="CompLess"/>
+ <div key="message" highlight="CompMsg">{this.context.message}</div>
+ <div key="itemsContainer" class="completion-items-container">
+ <div key="items" highlight="Completions"/>
+ </div>
+ <div key="waiting" highlight="CompMsg">{ItemList.WAITING_MESSAGE}</div>
+ <div key="down" highlight="CompMore"/>
+ </div>
+ </div>,
+
+ get doc() this.parent.doc,
+ get win() this.parent.win,
+ get maxItems() this.parent.maxItems,
+
+ get itemCount() this.context.items.length,
+
+ /**
+ * Returns a function which will update the scroll offsets
+ * and heights of various DOM members.
+ * @private
+ */
+ get rescrollFunc() {
+ let container = this.nodes.itemsContainer;
+ let pos = DOM(container).rect.top;
+ let start = DOM(this.getRow(this.range.start)).rect.top;
+ let height = DOM(this.getRow(this.range.end - 1)).rect.bottom - start || 0;
+ let scroll = start + container.scrollTop - pos;
+
+ let win = this.win;
+ let row = this.selectedRow;
+ if (row && this.parent.minHeight) {
+ let { rect } = DOM(this.selectedRow);
+ var scrollY = this.win.scrollY + rect.bottom - this.win.innerHeight;
+ }
+
+ return function () {
+ container.style.height = height + "px";
+ container.scrollTop = scroll;
+ if (scrollY != null)
+ win.scrollTo(0, Math.max(scrollY, 0));
+ }
+ },
+
+ /**
+ * Reset this group for use with a new set of results.
+ */
+ reset: function reset() {
+ this.nodes = {};
+ this.generatedRange = ItemList.Range(0, 0);
+
+ DOM.fromXML(this.rootXML, this.doc, this.nodes);
+ },
+
+ /**
+ * Update this group after an asynchronous results push.
+ */
+ update: function update() {
+ this.generatedRange = ItemList.Range(0, 0);
+ DOM(this.nodes.items).empty();
+
+ if (this.context.message)
+ DOM(this.nodes.message).empty().append(<>{this.context.message}</>);
+
+ if (!this.selectedIdx > this.itemCount)
+ this.selectedIdx = null;
+ },
+
+ /**
+ * Updates the DOM to reflect the current state of this
+ * group.
+ * @private
+ */
+ draw: function draw() {
+ DOM(this.nodes.contents).toggle(!this.collapsed);
+ if (this.collapsed)
+ return;
+
+ DOM(this.nodes.message).toggle(this.context.message && this.range.start == 0);
+ DOM(this.nodes.waiting).toggle(this.context.incomplete && this.range.end <= this.itemCount);
+ DOM(this.nodes.up).toggle(this.range.start > 0);
+ DOM(this.nodes.down).toggle(this.range.end < this.itemCount);
+
+ if (!this.generatedRange.contains(this.range)) {
+ if (this.generatedRange.end == 0)
+ var [start, end] = this.range;
+ else {
+ start = this.range.start - (this.range.start <= this.generatedRange.start
+ ? this.maxItems / 2 : 0);
+ end = this.range.end + (this.range.end > this.generatedRange.end
+ ? this.maxItems / 2 : 0);
+ }
+
+ let range = ItemList.Range(Math.max(0, start - start % 2),
+ Math.min(this.itemCount, end));
+
+ let first;
+ for (let [i, row] in this.context.getRows(this.generatedRange.start,
+ this.generatedRange.end,
+ this.doc))
+ if (!range.contains(i))
+ DOM(row).remove();
+ else if (!first)
+ first = row;
+
+ let container = DOM(this.nodes.items);
+ let before = first ? DOM(first).closure.before
+ : DOM(this.nodes.items).closure.append;
+
+ for (let [i, row] in this.context.getRows(range.start, range.end,
+ this.doc)) {
+ if (i < this.generatedRange.start)
+ before(row);
+ else if (i >= this.generatedRange.end)
+ container.append(row);
+ if (i == this.selectedIdx)
+ this.selectedIdx = this.selectedIdx;
+ }
+
+ this.generatedRange = range;
+ }
+ },
+
+ getRow: function getRow(idx) this.context.getRow(idx, this.doc),
+
+ getOffset: function getOffset(idx) this.offsets.start + (idx || 0),
+
+ get selectedRow() this.getRow(this._selectedIdx),
+
+ get selectedIdx() this._selectedIdx,
+ set selectedIdx(idx) {
+ if (this.selectedRow && this._selectedIdx != idx)
+ DOM(this.selectedRow).attr("selected", null);
+
+ this._selectedIdx = idx;
+
+ if (this.selectedRow)
+ DOM(this.selectedRow).attr("selected", true);
+ }
+ }),
+
+ Range: Class.Memoize(function () {
+ let Range = Struct("ItemList.Range", "start", "end");
+ update(Range.prototype, {
+ contains: function contains(idx)
+ typeof idx == "number" ? idx >= this.start && idx < this.end
+ : this.contains(idx.start) &&
+ idx.end >= this.start && idx.end <= this.end
+ });
+ return Range;
+ })