You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
venv/PyCharm 2025.2.1.1/plugins/javascript-debugger/webConsole/WebConsole.js

637 lines
17 KiB

class NavigatablesHost {
constructor() {
this.firstNavigatable = null;
this.lastNavigatable = null;
}
addNavigatable(item) {
if (this.lastNavigatable) {
this.lastNavigatable.nextNavigatable = item;
item.prevNavigatable = this.lastNavigatable;
}
item.navigatableHost = this;
if (!this.firstNavigatable) {
this.firstNavigatable = item;
}
this.lastNavigatable = item;
}
addNavigatableBefore(nextItem, newItem) {
if (nextItem.prevNavigatable) {
nextItem.prevNavigatable.nextNavigatable = newItem;
newItem.prevNavigatable = nextItem.prevNavigatable;
} else if (this.firstNavigatable === nextItem) {
this.firstNavigatable = newItem;
}
newItem.navigatableHost = this;
nextItem.prevNavigatable = newItem;
newItem.nextNavigatable = nextItem;
}
getPreviousNavigatable(current) {
let prev = null;
if (current) {
prev = current.prevNavigatable;
while (prev) {
let elem;
if (prev instanceof NavigatablesHost) {
elem = prev.getFirstNavigatable();
} else {
elem = prev;
}
if (!isHidden(elem)) {
break;
}
prev = prev.prevNavigatable;
}
}
if (prev instanceof NavigatablesHost) {
return prev.getLastNavigatable();
}
if (!prev && this.navigatableHost) {
return this.navigatableHost.getPreviousNavigatable(this);
}
return prev;
}
getNextNavigatable(current) {
let next = null;
if (current) {
next = current.nextNavigatable;
while (next) {
let elem;
if (next instanceof NavigatablesHost) {
elem = next.getFirstNavigatable();
} else {
elem = next;
}
if (!isHidden(elem)) {
break;
}
next = next.nextNavigatable;
}
}
if (next instanceof NavigatablesHost) {
return next.getFirstNavigatable();
}
if (!next && this.navigatableHost) {
return this.navigatableHost.getNextNavigatable(this);
}
return next;
}
getFirstNavigatable() {
let first = this.firstNavigatable;
while (first) {
let elem;
if (first instanceof NavigatablesHost) {
elem = first.getFirstNavigatable();
} else {
elem = first;
}
if (!isHidden(elem)) {
break;
}
first = first.nextNavigatable;
}
if (first instanceof NavigatablesHost) {
return first.getFirstNavigatable();
}
return first;
}
getLastNavigatable() {
let last = this.lastNavigatable;
while (last) {
let elem;
if (last instanceof NavigatablesHost) {
elem = last.getFirstNavigatable();
} else {
elem = last;
}
if (!isHidden(elem)) {
break;
}
last = last.prevNavigatable;
}
if (last instanceof NavigatablesHost) {
return last.getLastNavigatable();
}
return last;
}
}
class WebConsole extends NavigatablesHost {
constructor() {
super();
this.messageContainer = document.getElementById("out");
this.renderedMessages = 0;
this._stickToEnd = true;
this.scrollStickGapSize = 60;
document.addEventListener('scroll', this._onScroll.bind(this), false);
document.addEventListener('mousedown', this._onMouseDown.bind(this), false);
document.onkeydown = this._onKeyEvent.bind(this);
this.rootGroup = new Group(this.messageContainer, null);
this.currentGroup = this.rootGroup;
this.maxRenderedCount = 2000;
this.deferredMap = new Map();
this.selectedElement = undefined;
this.firstMessage = undefined;
}
_onKeyEvent(e) {
e = e || window.event;
let consume = false;
if (e.keyCode === KEY_UP) {
consume = true;
this._selectUp();
} else if (e.keyCode === KEY_DOWN) {
consume = true;
this._selectDown();
} else if (e.keyCode === KEY_LEFT) {
let current = this.selectedElement;
while (current) {
if (current && current.collapseAction && current.collapseAction()) {
consume = true;
this._select(current.getFirstNavigatable());
break;
} else {
current = current.navigatableHost;
}
}
} else if (e.keyCode === KEY_RIGHT) {
let current = this.selectedElement;
while (current) {
if (current && current.expandAction && current.expandAction()) {
consume = true;
this._select(current.getFirstNavigatable());
break;
} else {
current = current.navigatableHost;
}
}
} else if (e.keyCode === KEY_ENTER) {
if (this.selectedElement && this.selectedElement.navigateAction) {
consume = true;
this.selectedElement.navigateAction()
}
}
if (consume) {
e.preventDefault();
}
}
/**
* @param {number} count
*/
setMaxRenderedCount(count) {
this.maxRenderedCount = count;
}
/**
* @return {!WebConsole}
*/
static instance() {
if (!WebConsole._instance)
WebConsole._instance = new WebConsole();
return WebConsole._instance;
}
/**
* @param {boolean} value
*/
setStickToEnd(value) {
this._stickToEnd = value;
setTimeout(() => {
this._stickToEnd = value;
if (value) {
this.scrollDown();
}
},0);
}
/**
* @param {!Event} event
*/
_onScroll(event) {
let bottom = document.scrollingElement.scrollHeight - window.innerHeight;
let curY = document.scrollingElement.scrollTop;
let stick = bottom - curY < this.scrollStickGapSize;
if (stick !== this._stickToEnd) {
this._stickToEnd = stick;
WebConsole._notifyStickToEndChange(stick);
}
}
/**
* @param {!Event} event
*/
_onMouseDown(event) {
let clickY = document.scrollingElement.scrollTop + event.clientY;
if (this._stickToEnd && document.scrollingElement.scrollHeight - clickY > this.scrollStickGapSize) {
this._stickToEnd = false;
WebConsole._notifyStickToEndChange(this._stickToEnd);
}
}
/**
* @param {boolean} state
*/
static _notifyStickToEndChange(state) {
callJVM("updateStickToEnd", [state]);
}
scrollDown() {
const scrollingElement = this.scrollingElement();
scrollingElement.scrollTop = scrollingElement.scrollHeight;
}
scrollingElement() {
return document.scrollingElement || document.body;
}
increaseLastMessageRepeatCount() {
let message = this.lastMessage;
if (message.repeatCounter) {
message.repeatCounter.increase()
} else {
message.repeatCounter = new RepeatCounter();
message.container.classList.add("repeated-message");
message.container.insertAdjacentElement('beforebegin', message.repeatCounter.container);
}
}
/**
* @param {string} text
* @param {boolean} caseSensitive
*/
findText(text, caseSensitive) {
return findText("#out", text, caseSensitive);
}
findNext() {
findNext();
}
findPrev() {
findPrev();
}
softWrap(state) {
document.getElementById("out").style.whiteSpace = state === true ? "pre-wrap" : "no-wrap";
}
clear() {
let root = this.messageContainer.parentNode;
this.messageContainer.remove();
this.messageContainer = createElement("div");
this.messageContainer.id = "out";
root.appendChild(this.messageContainer);
/** @var {Message} */
this.lastMessage = undefined;
this.rootGroup = new Group(this.messageContainer, null);
this.currentGroup = this.rootGroup;
this.renderedMessages = 0;
this.deferredMap.clear();
}
startTrace() {
let currentMessage = this.getCurrentMessage();
let currentContainer = currentMessage.container;
currentContainer.classList.add("trace");
currentContainer.classList.add("collapsed");
let groupContainer = createElement("div", "group-container");
let preview = createElement("span", "preview");
currentContainer.insertBefore(preview, WebConsole.isIcon(currentContainer.firstChild)
? currentContainer.firstChild.nextSibling
: currentContainer.firstChild);
while (preview.nextSibling) {
let nextSibling = preview.nextSibling;
nextSibling.remove();
preview.appendChild(nextSibling);
}
currentContainer.appendChild(groupContainer);
currentContainer.onclick = (event) => {
toggle_visibility(currentContainer)
};
currentMessage.expandAction = () => expand(currentContainer)
currentMessage.collapseAction = () => collapse(currentContainer)
currentMessage.container = groupContainer;
currentMessage.savedContainer = currentContainer;
}
static isIcon(element) {
return element.classList.contains("result-icon");
}
endTrace() {
let currentMessage = this.getCurrentMessage();
currentMessage.container = currentMessage.savedContainer;
}
startGroup(groupName, collapsed) {
let state = collapsed ? "collapsed" : "expanded";
let currentMessage = this.getCurrentMessage();
let container = currentMessage.container;
container.classList.remove("message");
container.classList.add("group");
container.classList.add(state);
let title = createElement("span", "preview");
title.appendChild(document.createTextNode(groupName));
let groupContainer = createElement("div", "group-container");
container.appendChild(title);
container.appendChild(groupContainer);
let clickHandler = (event) => {
event.stopPropagation();
toggle_visibility(container);
};
title.onclick = clickHandler;
container.onclick = (event) => {
if (event.target !== event.currentTarget) return;
clickHandler(event);
};
currentMessage.expandAction = () => expand(container)
currentMessage.collapseAction = () => collapse(container)
this.currentGroup = new Group(groupContainer, this.currentGroup);
}
endGroup() {
if (this.currentGroup.parent) {
this.currentGroup = this.currentGroup.parent;
}
}
startMessage(type, level, source) {
this._addMessage(new Message(type, level, source));
}
/**
* @param {Printable} printable
*/
print(printable) {
let currentMessage = this.getCurrentMessage();
let newNode = this._prepareNode(printable, currentMessage);
if (printable.deferred) {
newNode.message = currentMessage;
this.deferredMap.set(printable.id, newNode);
}
let currentBlock = currentMessage.container;
if (printable.type === PRINTABLE_TYPES.MESSAGE_LINK) {
currentBlock.insertAdjacentElement("beforebegin", newNode);
} else {
currentBlock.appendChild(newNode);
}
}
/**
* @param {Printable} printable
*/
_prepareNode(printable, currentMessage) {
let newNode;
if (isLinkType(printable.type)) {
newNode = createTextNode(printable);
newNode.onclick = (e) => {
e.stopPropagation();
callJVM("navigate", [printable.id]);
};
newNode.navigatable = true;
newNode.navigateAction = () => {callJVM("navigate", [printable.id])};
currentMessage.addNavigatable(newNode);
}
else if (printable.type === PRINTABLE_TYPES.TREE) {
let treeView = new TreeView(printable, this);
newNode = treeView.rootElement();
currentMessage.addNavigatable(treeView.rootItem);
}
else {
newNode = createTextNode(printable);
if (printable.iconURL != null) {
let icon = WebConsole._createDynamicIcon(printable, "node-icon");
newNode.insertAdjacentElement("afterbegin", icon);
}
}
newNode.classList.add(...printable.styleClasses);
return newNode;
}
/**
* @param {Printable} newPrintable
*/
resolveDeferred(newPrintable) {
let currentNode = this.deferredMap.get(newPrintable.deferredID);
let currentMessage = currentNode.message;
this.deferredMap.delete(newPrintable.deferredID);
let newNode = this._prepareNode(newPrintable, currentMessage);
currentNode.parentNode.replaceChild(newNode, currentNode);
}
/**
* @returns {Message}
*/
getCurrentMessage() {
if (!this.lastMessage) {
this._addMessage(new Message());
}
return this.lastMessage;
}
static _createIcon(...styles) {
return createElement("span", "icon", ...styles);
}
static _createDynamicIcon(valueMessage, ...styles) {
let iconElement = WebConsole._createIcon(...styles)
iconElement.style.backgroundImage = 'url(' + valueMessage.iconURL + ')';
return iconElement;
}
/**
* @param {Message} message
* @private
*/
_addMessage(message) {
if (!this.firstMessage) {
this.firstMessage = message;
}
message.root.onclick = (event) => {
if (event.target !== event.currentTarget) return;
this.selectedMessage = message;
this._select(message.root);
};
this.addNavigatable(message);
this.lastMessage = message;
this.currentGroup.add(message);
// groups counted as one rendered message
if (this.rootGroup === this.currentGroup) {
this.renderedMessages++;
}
if (this.renderedMessages > this.maxRenderedCount) {
this.hideMessages();
}
}
/**
* @param {Element} element
* @private
*/
_select(element) {
if (this.selectedElement) {
this.selectedElement.classList.remove("selected");
}
if (element) {
element.classList.add("selected");
element.scrollIntoViewIfNeeded(false);
}
this.selectedElement = element;
}
_selectUp() {
let next;
if (this.selectedElement) {
next = this.selectedElement.navigatableHost.getPreviousNavigatable(this.selectedElement);
} else {
next = this.getLastNavigatable();
}
this._select(next);
}
_selectDown() {
let next;
if (this.selectedElement) {
next = this.selectedElement.navigatableHost.getNextNavigatable(this.selectedElement);
} else {
next = this.getFirstNavigatable();
}
this._select(next);
}
hideMessages() {
if (this.renderedMessages <= this.maxRenderedCount) return;
const singleRun = this.renderedMessages - this.maxRenderedCount;
let messagesHolder;
if (this.messageContainer.firstChild.class instanceof MessagesHolder
&& this.messageContainer.firstChild.class.savedMessages.length < this.maxRenderedCount) {
messagesHolder = this.messageContainer.firstChild.class;
} else {
messagesHolder = new MessagesHolder();
this.messageContainer.insertBefore(messagesHolder.root, this.messageContainer.firstChild);
}
let currentMessage = this.messageContainer.firstChild.nextSibling;
let next = currentMessage.nextSibling;
for (let i = 0; i < singleRun && next; i++) {
currentMessage.remove();
this.renderedMessages--;
messagesHolder.savedMessages.push(currentMessage);
currentMessage = next;
next = currentMessage.nextSibling;
}
}
}
class Message extends NavigatablesHost {
constructor(type, level, source) {
super();
this.container = createElement("div", "message");
this.root = createElement("div", "message-wrapper");
this.root.appendChild(this.container);
this.addNavigatable(this.root);
if (level === 'level-error' || level === 'level-warning' || level === 'level-info') {
this.setIcon(WebConsole._createIcon("result-icon"));
}
else if (type === 'EVAL_IN') {
this.root.classList.add("message-input");
this.setIcon(WebConsole._createIcon("prompt-in", "result-icon"));
} else if (type === 'EVAL_OUT') {
this.root.classList.add("message-result");
this.setIcon(WebConsole._createIcon("prompt-out", "result-icon"));
}
this.root.classList.add(level);
this.root.classList.add(source);
}
setIcon(icon) {
this.icon = icon;
this.container.appendChild(icon);
}
}
class Group {
/**
* @param {!Element} container
* @param {?Group} parent
*/
constructor(container, parent) {
this.container = container;
this.parent = parent;
}
/**
* @param {!Message} message
*/
add(message) {
this.container.appendChild(message.root);
message.root.group = this;
}
}
class MessagesHolder {
constructor() {
this.root = createElement("div", "saved-messages");
this.root.appendChild(document.createTextNode("Show previous logs"));
this.root.addEventListener("click", this.expand.bind(this));
this.savedMessages = [];
this.root.class = this;
}
expand() {
let parent = this.root.parentElement;
let anchor = this.root;
for (let message of this.savedMessages) {
parent.insertBefore(message, anchor);
}
this.savedMessages = [];
this.root.remove();
}
}
class RepeatCounter {
constructor() {
this.count = 2;
this.container = createElement("label", "repeat-counter");
this.container.textContent = this.count;
}
increase() {
this.count++;
this.container.textContent = this.count;
}
}