src/languageTools/DefaultProviders.js
/*
* Copyright (c) 2019 - present Adobe. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
/*global Map*/
/* eslint-disable indent */
/* eslint max-len: ["error", { "code": 200 }], no-invalid-this: 0*/
define(function (require, exports, module) {
"use strict";
var _ = brackets.getModule("thirdparty/lodash");
var EditorManager = require('editor/EditorManager'),
DocumentManager = require('document/DocumentManager'),
ExtensionUtils = require("utils/ExtensionUtils"),
CommandManager = require("command/CommandManager"),
Commands = require("command/Commands"),
TokenUtils = require("utils/TokenUtils"),
StringMatch = require("utils/StringMatch"),
CodeInspection = require("language/CodeInspection"),
PathConverters = require("languageTools/PathConverters"),
matcher = new StringMatch.StringMatcher({
preferPrefixMatches: true
});
ExtensionUtils.loadStyleSheet(module, "styles/default_provider_style.css");
function setClient(client) {
if (client) {
this.client = client;
}
}
function CodeHintsProvider(client) {
this.client = client;
this.query = "";
this.ignoreQuery = ["-", "->", ">", ":", "::", "(", "()", ")", "[", "[]", "]", "{", "{}", "}"];
}
CodeHintsProvider.prototype.setClient = setClient;
function formatTypeDataForToken($hintObj, token) {
$hintObj.addClass('brackets-hints-with-type-details');
if (token.detail) {
if (token.detail.trim() !== '?') {
if (token.detail.length < 30) {
$('<span>' + token.detail.split('->').join(':').toString().trim() + '</span>').appendTo($hintObj).addClass("brackets-hints-type-details");
}
$('<span>' + token.detail.split('->').join(':').toString().trim() + '</span>').appendTo($hintObj).addClass("hint-description");
}
} else {
if (token.keyword) {
$('<span>keyword</span>').appendTo($hintObj).addClass("brackets-hints-keyword");
}
}
if (token.documentation) {
$hintObj.attr('title', token.documentation);
$('<span></span>').text(token.documentation.trim()).appendTo($hintObj).addClass("hint-doc");
}
}
function filterWithQueryAndMatcher(hints, query) {
var matchResults = $.map(hints, function (hint) {
var searchResult = matcher.match(hint.label, query);
if (searchResult) {
for (var key in hint) {
searchResult[key] = hint[key];
}
}
return searchResult;
});
return matchResults;
}
CodeHintsProvider.prototype.hasHints = function (editor, implicitChar) {
if (!this.client) {
return false;
}
var serverCapabilities = this.client.getServerCapabilities();
if (!serverCapabilities || !serverCapabilities.completionProvider) {
return false;
}
return true;
};
CodeHintsProvider.prototype.getHints = function (implicitChar) {
if (!this.client) {
return null;
}
var editor = EditorManager.getActiveEditor(),
pos = editor.getCursorPos(),
docPath = editor.document.file._path,
$deferredHints = $.Deferred(),
self = this;
this.client.requestHints({
filePath: docPath,
cursorPos: pos
}).done(function (msgObj) {
var context = TokenUtils.getInitialContext(editor._codeMirror, pos),
hints = [];
self.query = context.token.string.slice(0, context.pos.ch - context.token.start);
if (msgObj) {
var res = msgObj.items,
filteredHints = filterWithQueryAndMatcher(res, self.query);
StringMatch.basicMatchSort(filteredHints);
filteredHints.forEach(function (element) {
var $fHint = $("<span>")
.addClass("brackets-hints");
if (element.stringRanges) {
element.stringRanges.forEach(function (item) {
if (item.matched) {
$fHint.append($("<span>")
.append(_.escape(item.text))
.addClass("matched-hint"));
} else {
$fHint.append(_.escape(item.text));
}
});
} else {
$fHint.text(element.label);
}
$fHint.data("token", element);
formatTypeDataForToken($fHint, element);
hints.push($fHint);
});
}
$deferredHints.resolve({
"hints": hints
});
}).fail(function () {
$deferredHints.reject();
});
return $deferredHints;
};
CodeHintsProvider.prototype.insertHint = function ($hint) {
var editor = EditorManager.getActiveEditor(),
cursor = editor.getCursorPos(),
token = $hint.data("token"),
txt = null,
query = this.query,
shouldIgnoreQuery = this.ignoreQuery.includes(query),
inclusion = shouldIgnoreQuery ? "" : query,
start = {
line: cursor.line,
ch: cursor.ch - inclusion.length
},
end = {
line: cursor.line,
ch: cursor.ch
};
txt = token.label;
if (token.textEdit && token.textEdit.newText) {
txt = token.textEdit.newText;
start = {
line: token.textEdit.range.start.line,
ch: token.textEdit.range.start.character
};
end = {
line: token.textEdit.range.end.line,
ch: token.textEdit.range.end.character
};
}
if (editor) {
editor.document.replaceRange(txt, start, end);
}
// Return false to indicate that another hinting session is not needed
return false;
};
function ParameterHintsProvider(client) {
this.client = client;
}
ParameterHintsProvider.prototype.setClient = setClient;
ParameterHintsProvider.prototype.hasParameterHints = function (editor, implicitChar) {
if (!this.client) {
return false;
}
var serverCapabilities = this.client.getServerCapabilities();
if (!serverCapabilities || !serverCapabilities.signatureHelpProvider) {
return false;
}
return true;
};
ParameterHintsProvider.prototype.getParameterHints = function () {
if (!this.client) {
return null;
}
var editor = EditorManager.getActiveEditor(),
pos = editor.getCursorPos(),
docPath = editor.document.file._path,
$deferredHints = $.Deferred();
this.client.requestParameterHints({
filePath: docPath,
cursorPos: pos
}).done(function (msgObj) {
let paramList = [];
let label;
let activeParameter;
if (msgObj) {
let res;
res = msgObj.signatures;
activeParameter = msgObj.activeParameter;
if (res && res.length) {
res.forEach(function (element) {
label = element.documentation;
let param = element.parameters;
param.forEach(ele => {
paramList.push({
label: ele.label,
documentation: ele.documentation
});
});
});
$deferredHints.resolve({
parameters: paramList,
currentIndex: activeParameter,
functionDocumentation: label
});
} else {
$deferredHints.reject();
}
} else {
$deferredHints.reject();
}
}).fail(function () {
$deferredHints.reject();
});
return $deferredHints;
};
/**
* Utility function to make the jump
* @param {Object} curPos - target postion for the cursor after the jump
*/
function setJumpPosition(curPos) {
EditorManager.getCurrentFullEditor().setCursorPos(curPos.line, curPos.ch, true);
}
function JumpToDefProvider(client) {
this.client = client;
}
JumpToDefProvider.prototype.setClient = setClient;
JumpToDefProvider.prototype.canJumpToDef = function (editor, implicitChar) {
if (!this.client) {
return false;
}
var serverCapabilities = this.client.getServerCapabilities();
if (!serverCapabilities || !serverCapabilities.definitionProvider) {
return false;
}
return true;
};
/**
* Method to handle jump to definition feature.
*/
JumpToDefProvider.prototype.doJumpToDef = function () {
if (!this.client) {
return null;
}
var editor = EditorManager.getFocusedEditor(),
pos = editor.getCursorPos(),
docPath = editor.document.file._path,
docPathUri = PathConverters.pathToUri(docPath),
$deferredHints = $.Deferred();
this.client.gotoDefinition({
filePath: docPath,
cursorPos: pos
}).done(function (msgObj) {
//For Older servers
if (Array.isArray(msgObj)) {
msgObj = msgObj[msgObj.length - 1];
}
if (msgObj && msgObj.range) {
var docUri = msgObj.uri,
startCurPos = {};
startCurPos.line = msgObj.range.start.line;
startCurPos.ch = msgObj.range.start.character;
if (docUri !== docPathUri) {
let documentPath = PathConverters.uriToPath(docUri);
CommandManager.execute(Commands.FILE_OPEN, {
fullPath: documentPath
})
.done(function () {
setJumpPosition(startCurPos);
$deferredHints.resolve();
});
} else { //definition is in current document
setJumpPosition(startCurPos);
$deferredHints.resolve();
}
}
}).fail(function () {
$deferredHints.reject();
});
return $deferredHints;
};
function LintingProvider() {
this._results = new Map();
this._promiseMap = new Map();
this._validateOnType = false;
}
LintingProvider.prototype.setClient = setClient;
LintingProvider.prototype.clearExistingResults = function (filePath) {
var filePathProvided = !!filePath;
if (filePathProvided) {
this._results.delete(filePath);
this._promiseMap.delete(filePath);
} else {
//clear all results
this._results.clear();
this._promiseMap.clear();
}
};
/**
* Publish the diagnostics information related to current document
* @param {Object} msgObj - json object containing information associated with 'textDocument/publishDiagnostics' notification from server
*/
LintingProvider.prototype.setInspectionResults = function (msgObj) {
let diagnostics = msgObj.diagnostics,
filePath = PathConverters.uriToPath(msgObj.uri),
errors = [];
errors = diagnostics.map(function (obj) {
return {
pos: {
line: obj.range.start.line,
ch: obj.range.start.character
},
message: obj.message,
type: (obj.severity === 1 ? CodeInspection.Type.ERROR : (obj.severity === 2 ? CodeInspection.Type.WARNING : CodeInspection.Type.META))
};
});
this._results.set(filePath, {
errors: errors
});
if(this._promiseMap.get(filePath)) {
this._promiseMap.get(filePath).resolve(this._results.get(filePath));
this._promiseMap.delete(filePath);
}
if (this._validateOnType) {
var editor = EditorManager.getActiveEditor(),
docPath = editor ? editor.document.file._path : "";
if (filePath === docPath) {
CodeInspection.requestRun();
}
}
};
LintingProvider.prototype.getInspectionResultsAsync = function (fileText, filePath) {
var result = $.Deferred();
if (this._results.get(filePath)) {
return result.resolve(this._results.get(filePath));
}
this._promiseMap.set(filePath, result);
return result;
};
LintingProvider.prototype.getInspectionResults = function (fileText, filePath) {
return this._results.get(filePath);
};
function serverRespToSearchModelFormat(msgObj) {
var referenceModel = {},
result = $.Deferred();
if(!(msgObj && msgObj.length && msgObj.cursorPos)) {
return result.reject();
}
referenceModel.results = {};
referenceModel.numFiles = 0;
var fulfilled = 0;
msgObj.forEach((element, i) => {
var filePath = PathConverters.uriToPath(element.uri);
DocumentManager.getDocumentForPath(filePath)
.done(function(doc) {
var startRange = {line: element.range.start.line, ch: element.range.start.character};
var endRange = {line: element.range.end.line, ch: element.range.end.character};
var match = {
start: startRange,
end: endRange,
highlightOffset: 0,
line: doc.getLine(element.range.start.line)
};
if(!referenceModel.results[filePath]) {
referenceModel.numFiles = referenceModel.numFiles + 1;
referenceModel.results[filePath] = {"matches": []};
}
if(!referenceModel.queryInfo || msgObj.cursorPos.line === startRange.line) {
referenceModel.queryInfo = doc.getRange(startRange, endRange);
}
referenceModel.results[filePath]["matches"].push(match);
}).always(function() {
fulfilled++;
if(fulfilled === msgObj.length) {
referenceModel.numMatches = msgObj.length;
referenceModel.allResultsAvailable = true;
result.resolve(referenceModel);
}
});
});
return result.promise();
}
function ReferencesProvider(client) {
this.client = client;
}
ReferencesProvider.prototype.setClient = setClient;
ReferencesProvider.prototype.hasReferences = function() {
if (!this.client) {
return false;
}
var serverCapabilities = this.client.getServerCapabilities();
if (!serverCapabilities || !serverCapabilities.referencesProvider) {
return false;
}
return true;
};
ReferencesProvider.prototype.getReferences = function(hostEditor, curPos) {
var editor = hostEditor || EditorManager.getActiveEditor(),
pos = curPos || editor ? editor.getCursorPos() : null,
docPath = editor.document.file._path,
result = $.Deferred();
if (this.client) {
this.client.findReferences({
filePath: docPath,
cursorPos: pos
}).done(function(msgObj){
if(msgObj && msgObj.length) {
msgObj.cursorPos = pos;
serverRespToSearchModelFormat(msgObj)
.done(result.resolve)
.fail(result.reject);
} else {
result.reject();
}
}).fail(function(){
result.reject();
});
return result.promise();
}
return result.reject();
};
exports.CodeHintsProvider = CodeHintsProvider;
exports.ParameterHintsProvider = ParameterHintsProvider;
exports.JumpToDefProvider = JumpToDefProvider;
exports.LintingProvider = LintingProvider;
exports.ReferencesProvider = ReferencesProvider;
exports.serverRespToSearchModelFormat = serverRespToSearchModelFormat;
});