c9-core/plugins/c9.ide.terminal/aceterm/hover_link.js

412 wiersze
14 KiB
JavaScript

define(function(require, exports, module) {
"use strict";
var oop = require("ace/lib/oop");
var event = require("ace/lib/event");
var Range = require("ace/range").Range;
var EventEmitter = require("ace/lib/event_emitter").EventEmitter;
var HoverLink = function(editor) {
if (editor.hoverLink)
return;
editor.hoverLink = this;
this.editor = editor;
this.update = this.update.bind(this);
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseOut = this.onMouseOut.bind(this);
this.onClick = this.onClick.bind(this);
this.onContextMenu = this.onContextMenu.bind(this);
event.addListener(editor.renderer.scroller, "mousemove", this.onMouseMove);
event.addListener(editor.renderer.content, "mouseout", this.onMouseOut);
event.addListener(editor.renderer.content, "click", this.onClick);
event.addListener(editor.renderer.container, "contextmenu", this.onContextMenu);
};
(function(){
oop.implement(this, EventEmitter);
this.token = {};
this.range = new Range();
this.update = function() {
this.$timer = null;
var editor = this.editor;
var renderer = editor.renderer;
var canvasPos = renderer.scroller.getBoundingClientRect();
var offset = (this.x + renderer.scrollLeft - canvasPos.left - renderer.$padding) / renderer.characterWidth;
var row = Math.floor((this.y + renderer.scrollTop - canvasPos.top) / renderer.lineHeight);
var col = Math.round(offset);
var screenPos = {row: row, column: col, side: offset - col > 0 ? 1 : -1};
var session = editor.session;
var docPos = session.screenToDocumentPosition(screenPos.row, screenPos.column);
var selectionRange = editor.selection.getRange();
if (!selectionRange.isEmpty()) {
if (selectionRange.start.row <= row && selectionRange.end.row >= row)
return this.clear();
}
var line = editor.session.getLine(docPos.row);
if (docPos.column == line.length) {
var clippedPos = editor.session.documentToScreenPosition(docPos.row, docPos.column);
if (clippedPos.column != screenPos.column) {
return this.clear();
}
}
var token = this.findLink(docPos.row, docPos.column);
// var isFocused = editor.isFocused();
this.link = token;
if (!token) {
return this.clear();
}
if (!this.isOpen) {
this.isOpen = true;
}
// token.isFocused = isFocused;
editor.renderer.setCursorStyle("pointer");
session.removeMarker(this.marker);
this.range = this.getRange(token);
this.marker = session.addMarker(this.range, "ace_link_marker", "text", true);
};
this.clear = function() {
if (this.isOpen) {
this.editor.session.removeMarker(this.marker);
this.editor.renderer.setCursorStyle("");
this.isOpen = false;
}
};
this.getMatchAround = function(regExp, string, col) {
var match;
regExp.lastIndex = 0;
string.replace(regExp, function(str) {
var offset = arguments[arguments.length-2];
var length = str.length;
if (offset <= col && offset + length >= col)
match = {
start: offset,
value: str
};
});
return match;
};
this.onClick = function(e) {
if (this.link && this.isOpen) { // && this.link.isFocused
if (this.editor.selection.isEmpty()) {
this.editor.selection.setSelectionRange(this.range);
this.lastRange = this.range;
}
this.link.editor = this.editor;
this.link.x = e.clientX;
this.link.y = e.clientY;
this.link.metaKey = e.metaKey;
this.link.ctrlKey = e.ctrlKey;
this._signal("open", this.link);
}
};
this.onContextMenu = function(e) {
var range = this.editor.selection.getRange();
if (!range.isEmpty()) {
if (this.lastRange && range.isEqual(this.lastRange)) {
this.editor.selection.clearSelection();
this.update();
}
}
if (this.link && this.isOpen) {
if (this.editor.selection.isEmpty()) {
this.editor.selection.setSelectionRange(this.range);
this.lastRange = this.range;
}
}
};
this.getRange = function(token) {
var session = this.editor.session;
var startCol = token.start;
var row = token.row;
var line = session.getLine(row);
while (line && line.length < startCol) {
startCol -= line.length;
row++;
line = session.getLine(row);
}
var startRow = row;
var endCol = startCol + token.value.length;
while (line && line.length < endCol) {
endCol -= line.length;
row++;
line = session.getLine(row);
}
var endRow = row;
return new Range(startRow, startCol, endRow, endCol);
};
this.findLink = function(row, column) {
var editor = this.editor;
var session = editor.session;
var lineData = session.getLineData(row);
var prevLineData = session.getLineData(row - 1);
var line = session.getLine(row);
for (var i = 1; lineData && lineData.wrapped; i++) {
line += session.getLine(row + i);
lineData = session.getLineData(row + i);
}
while (prevLineData && prevLineData.wrapped) {
row --;
var prevLine = session.getLine(row);
column += prevLine.length;
line = prevLine + line;
prevLineData = session.getLineData(row - 1);
}
lineData = session.getLineData(row);
var match = this.getMatchAround(/https?:\/\/[^\s"']+|[~\/\w.]([:.~/\w-_]|\\.)+/g, line, column);
if (!match)
return;
match.row = row;
var value = match.value;
if (/^https?:|\bc9.io\b|(\d{1,3}\b.?){4}|localhost/.test(value)) {
match.type = "link";
var m = /((https?:\/\/)?(\d{1,3}\b.?){4}(:\d+[^\d\/]|[^:\d\/]))/.exec(value);
if (m)
value = m[0].slice(0, -1);
value = value.replace(/[>)}\].,;:]+$/, "");
match.value = value;
return match;
}
var prompt = this.findPrompt(row);
if (!prompt) {
if (/^(\/|~|\/?\w:[\\/])/.test(value)) { // windows or unix absolute path
match.type = "path";
match.value = value.replace(/["'>)}\].,;:]+$/, "");
return match;
}
return;
}
if (lineData.prompt && match.start <= prompt.index) {
match.value = value = value.substr(prompt.index - match.start);
match.start = prompt.index;
if (match.start > column || !value)
return;
return;
}
match.command = prompt.command;
match.args = prompt.args;
match.basePath = prompt.path;
if (prompt.command === "ls") {
match.type = "path";
if (lineData.prompt)
return;
// update basepath for ls /dir/name
if (prompt.args)
match.basePath = this.findBasePath(prompt.args, prompt.path);
if (prompt.args && /( |^)(--help|-h)\b/.test(prompt.args))
return;
var longStyle = /(^|\s)[ld\-][\-rwx]+\s/.test(line);
if (longStyle) {
if (/[*@|=>]$/.test(line))
line = line.slice(0, -1);
if (match.start + value.length < line.length) {
var isLink = line[0] == "l" && line.substr(match.start + value.length, 4) == " -> ";
if (!isLink)
return;
}
} else {
var before = line.substring(0, match.start).match(/[^\s\\\/@"']+ $/);
var after = line.substr(match.start + value.length).match(/^ [^\s\\\/@"']+/);
if (before && !/\d+/.test(before[0])) {
value = before[0] + value;
match.start -= before[0].length;
}
if (after) {
value += after[0];
}
match.value = value;
return match;
}
}
else if (prompt.command === "find") {
if (match.start !== 0 || match.length < line.length)
return;
if (prompt.args)
match.basePath = this.findBasePath(prompt.args, prompt.path);
match.type = "path";
}
else if (prompt.command === "grep") {
if (match.start !== 0)
return;
match.type = "path";
match.value = value.replace(/:[^\d][^:]*$/, "");
// match.basePath = "";
}
else if (prompt.command === "ack" || prompt.command === "ag" || prompt.command === "ack-grep") {
match.type = "path";
var fontColor = lineData[column] && lineData[column][0];
if (match.start !== 0) {
if (fontColor == session.term.defAttr)
return;
var col = column;
while (lineData[col] && lineData[col][0] == fontColor)
col--;
match.start = col + 1;
col = column;
while (lineData[col] && lineData[col][0] == fontColor)
col++;
match.value = line.substring(match.start, col);
}
var jumpLine = line.match(/^(\d*:)?/)[0];
var jumpColumn = Math.max(match.start - jumpLine.length, 0);
if (match.start == 0 && jumpLine)
match.value = jumpLine;
var pathLine = line;
while (/^\d+/.test(pathLine) && row > prompt.row) {
lineData = session.getLineData(row);
if (!lineData.wrapped) {
pathLine = session.getLine(row);
}
row--;
}
match.path = pathLine;
if (jumpLine)
match.path += ":" + jumpLine + jumpColumn;
if (match.start == 0 && jumpLine)
match.action = "open";
}
else if (/^(~|\.\.?)?[\/\\]/.test(value) || /\w:[\\]/.test(value)) {
match.type = "path";
match.value = value.replace(/['">)}\].,;:]+$/, "");
}
else if (/^[ab]?\//.test(value) && /^([+\-]{3}|diff)/.test(line)) { // diff
match.type = "path";
match.basePath = "";
match.start++;
match.value = value.substr(2);
}
else if (prompt.command === "git") { // git status
var prefix = line.substr(0, match.start);
if (match.start + value.length == line.length
&& /^(#|[ MDR?A]{2})\s+([\w\s]+:\s+)?$/.test(prefix)
) {
match.type = "path";
} else {
value = value.replace(/[.:]$/, "");
if (value.match(/[/][^/]*\.\w+[.:]$/) || prefix.match(/( in |merging )$/)) {
match.type = "path";
match.value = value;
} else {
return;
}
}
} else {
return;
}
return match;
};
this.findBasePath = function(args, basePath) {
var argList = args.split(" ").filter(function(x) {
return x[0] != "-";
});
var pathArg = argList[argList.length - 1];
if (pathArg) {
if (pathArg[0] === "~" || pathArg[0] === "/")
return pathArg;
return basePath + "/" + pathArg;
}
return basePath;
};
this.findPrompt = function(row) {
var promptRe = /([~\/](?:[^\\\s'"\(<]|\\.)*)[^$]*?\$/;
var editor = this.editor;
var session = editor.session;
var prompt, args, command, promptRow;
do {
var lineData = session.getLineData(row);
if (lineData.prompt)
return lineData.prompt;
var line = session.getLine(row);
var m = promptRe.exec(line);
if (m) {
line = line.substr(m.index + m[0].length);
var m2 = /^\s*([\w\-]+)/.exec(line);
if (m2) {
command = m2[1];
args = line.substr(m2[0].length);
}
if (!prompt || lineData.isUserInput) {
prompt = {
path: m[1],
command: command,
index: m.index,
args: args,
lineData: lineData,
row: row
};
}
if (lineData.isUserInput)
break;
}
} while (row-- > 0);
if (prompt && promptRow < session.term.ybase)
prompt.lineData.prompt = prompt;
return prompt;
};
this.onMouseMove = function(e) {
if (this.editor.$mouseHandler.isMousePressed) {
if (!this.editor.selection.isEmpty())
this.clear();
return;
}
if (this.x == e.clientX && this.y == e.clientY)
return;
this.x = e.clientX;
this.y = e.clientY;
if (this.isOpen) {
this.lastT = e.timeStamp;
}
this.update();
};
this.onMouseOut = function(e) {
this.clear();
this.$timer = clearTimeout(this.$timer);
};
this.destroy = function() {
this.onMouseOut();
event.removeListener(this.editor.renderer.scroller, "mousemove", this.onMouseMove);
event.removeListener(this.editor.renderer.content, "mouseout", this.onMouseOut);
delete this.editor.hoverLink;
};
}).call(HoverLink.prototype);
exports.HoverLink = HoverLink;
});