Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
551 lines (472 sloc) 17.1 KB
#include "JSCodeCompletion.h"
#include "NodeJSLocator.h"
#include "NoteJSWorkspace.h"
#include "SocketAPI/clSocketClient.h"
#include "WebToolsConfig.h"
#include "asyncprocess.h"
#include "clTernServer.h"
#include "clTernWorkerThread.h"
#include "cl_standard_paths.h"
#include "entry.h"
#include "file_logger.h"
#include "fileutils.h"
#include "globals.h"
#include "ieditor.h"
#include "imanager.h"
#include "processreaderthread.h"
#include "wxCodeCompletionBoxEntry.h"
#include <wx/log.h>
#include <wx/msgdlg.h>
#include <wx/regex.h>
#include <wx/stc/stc.h>
#include "clNodeJS.h"
clTernServer::clTernServer(JSCodeCompletion* cc)
: m_jsCCManager(cc)
, m_tern(NULL)
, m_goingDown(false)
, m_workerThread(NULL)
, m_fatalError(false)
, m_port(wxNOT_FOUND)
, m_recycleCount(0)
{
Bind(wxEVT_ASYNC_PROCESS_OUTPUT, &clTernServer::OnTernOutput, this);
Bind(wxEVT_ASYNC_PROCESS_TERMINATED, &clTernServer::OnTernTerminated, this);
m_port = WebToolsConfig::Get().GetPortNumber();
}
clTernServer::~clTernServer() {}
void clTernServer::OnTernOutput(clProcessEvent& event)
{
PrintMessage(event.GetOutput());
}
static size_t crash_count = 0;
void clTernServer::OnTernTerminated(clProcessEvent& event)
{
wxDELETE(m_tern);
if(m_goingDown || !m_jsCCManager->IsEnabled()) { return; }
#if defined(__WXMSW__) && !defined(NDEBUG)
HANDLE hProcess = ::GetCurrentProcess();
DWORD handleCount;
::GetProcessHandleCount(hProcess, &handleCount);
clDEBUG1() << "Tern process termianted. Number of handles:" << (int)handleCount;
::CloseHandle(hProcess);
#endif
PrintMessage("Tern server terminated, will restart it\n");
++crash_count;
Start(m_workingDirectory);
}
bool clTernServer::Start(const wxString& workingDirectory)
{
if(m_fatalError) return false;
if(!m_jsCCManager->IsEnabled()) return true;
if(!WebToolsConfig::Get().IsNodeInstalled()) { return true; }
if(!WebToolsConfig::Get().IsTernInstalled()) { return true; }
m_workingDirectory = workingDirectory;
WebToolsConfig& conf = WebToolsConfig::Get();
wxString nodeExe = conf.GetNodejs();
::WrapWithQuotes(nodeExe);
wxString ternScriptString = conf.GetTernScript().GetFullPath();
::WrapWithQuotes(ternScriptString);
wxString command;
command << nodeExe << " " << ternScriptString << " --persistent --port " << m_port;
if(conf.HasJavaScriptFlag(WebToolsConfig::kJSEnableVerboseLogging)) { command << " --verbose"; }
// Create a .tern-project file
if(m_workingDirectory.IsEmpty()) { m_workingDirectory = clStandardPaths::Get().GetUserDataDir(); }
wxFileName ternConfig(m_workingDirectory, ".tern-project");
wxString content = conf.GetTernProjectFile();
if(!FileUtils::WriteFileContent(ternConfig, content)) {
::wxMessageBox(_("Could not write tern project file: ") + ternConfig.GetFullPath(), "CodeLite",
wxICON_ERROR | wxOK | wxCENTER);
PrintMessage("Could not write tern project file: " + ternConfig.GetFullPath());
m_fatalError = true;
return false;
}
PrintMessage(wxString() << "Starting: " << command << "\n");
m_tern = ::CreateAsyncProcess(this, command, IProcessCreateDefault, m_workingDirectory);
if(!m_tern) {
PrintMessage("Failed to start Tern server!");
return false;
}
return true;
}
void clTernServer::Terminate()
{
m_goingDown = true;
if(m_tern) { m_tern->Terminate(); }
wxDELETE(m_tern);
// Stop the worker thread
if(m_workerThread) { m_workerThread->Stop(); }
wxDELETE(m_workerThread);
}
bool clTernServer::PostCCRequest(IEditor* editor)
{
// Sanity
if(m_workerThread) return false; // another request is in progress
if(m_port == wxNOT_FOUND) return false; // don't know tern's port
++m_recycleCount;
wxStyledTextCtrl* ctrl = editor->GetCtrl();
// Prepare the request
JSON root(cJSON_Object);
JSONItem query = JSONItem::createObject("query");
root.toElement().append(query);
query.addProperty("type", wxString("completions"));
query.addProperty("file", wxString("#0"));
query.append(CreateLocation(ctrl));
query.addProperty("docs", true);
query.addProperty("urls", true);
query.addProperty("includeKeywords", true);
query.addProperty("types", true);
JSONItem files = CreateFilesArray(editor);
root.toElement().append(files);
clTernWorkerThread::Request* req = new clTernWorkerThread::Request;
req->jsonRequest = root.toElement().FormatRawString();
req->filename = editor->GetFileName().GetFullPath();
req->type = clTernWorkerThread::kCodeCompletion;
// Create the worker thread and start the request
m_workerThread = new clTernWorkerThread(this);
m_workerThread->Start();
m_workerThread->Add(req);
return true;
}
void clTernServer::PrintMessage(const wxString& message)
{
wxString msg;
msg << message;
msg.Trim().Trim(false);
clDEBUG() << msg;
}
void clTernServer::RecycleIfNeeded(bool force)
{
if(m_tern && ((m_recycleCount >= 100) || force)) {
m_recycleCount = 0;
m_tern->Terminate();
} else if(!m_tern) {
// Tern was never started, start it now
Start(m_workingDirectory);
}
}
wxString clTernServer::PrepareDoc(const wxString& doc, const wxString& url)
{
wxString d = doc;
d.Replace("\\n", " ");
wxArrayString tokens = ::wxStringTokenize(d, " ", wxTOKEN_STRTOK);
wxString content;
wxString curline;
for(size_t i = 0; i < tokens.size(); ++i) {
curline << tokens.Item(i) << " ";
if(curline.length() > 80) {
content << curline << "\n";
curline.clear();
}
}
if(!curline.IsEmpty()) { content << curline << "\n"; }
if(!url.IsEmpty()) { content << "@link " << url; }
return content;
}
void clTernServer::ProcessType(const wxString& type, wxString& signature, wxString& retValue, int& imgID)
{
// initialize values
imgID = wxNOT_FOUND;
retValue.clear();
signature.clear();
if(type.StartsWith("fn(")) {
imgID = 9; // function
int depth = 1;
wxString tmp = type.Mid(3); // skip fn(
signature = "(";
bool cont = true;
while(!tmp.IsEmpty() && cont) {
wxChar ch = tmp.GetChar(0);
tmp.Remove(0, 1);
switch(ch) {
case '(':
++depth;
signature << "(";
break;
case ')':
--depth;
if(depth == 0) cont = false;
signature << ")";
break;
default:
signature << ch;
break;
}
}
tmp.Trim().Trim(false);
if(tmp.StartsWith("->")) {
tmp = tmp.Mid(2);
retValue = tmp;
}
} else {
imgID = 3; // Member public
signature.clear();
retValue = type;
}
}
void clTernServer::ProcessOutput(const wxString& output, wxCodeCompletionBoxEntry::Vec_t& entries)
{
// code completion response:
// ================================
// {
// "start": 78,
// "end": 78,
// "isProperty": true,
// "isObjectKey": false,
// "completions": [
// {
// "name": "concat",
// "type": "fn(other: [?])",
// "doc": "Returns a new array comprised of this array joined with other array(s) and/or value(s).",
// "url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/concat"
// },
// {
// "name": "every",
// "type": "fn(test: fn(elt: ?, i: number) -> bool, context?: ?) -> bool",
// "doc": "Tests whether all elements in the array pass the test implemented by the provided function.",
// "url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/every"
// }]}
entries.clear();
JSON root(output);
JSONItem completionsArr = root.toElement().namedObject("completions");
for(int i = 0; i < completionsArr.arraySize(); ++i) {
JSONItem item = completionsArr.arrayItem(i);
wxString name = item.namedObject("name").toString();
wxString doc = item.namedObject("doc").toString();
wxString url = item.namedObject("url").toString();
bool isKeyword = item.namedObject("isKeyword").toBool();
int imgId;
if(!isKeyword) {
doc = this->PrepareDoc(doc, url);
wxString type = item.namedObject("type").toString();
wxString sig, ret;
ProcessType(type, sig, ret, imgId);
// Remove double quotes
name.StartsWith("\"", &name);
name.EndsWith("\"", &name);
wxCodeCompletionBoxEntry::Ptr_t entry = wxCodeCompletionBoxEntry::New(name /* + sig*/, imgId);
entry->SetComment(doc);
entries.push_back(entry);
} else {
imgId = 17; // keyword
wxCodeCompletionBoxEntry::Ptr_t entry = wxCodeCompletionBoxEntry::New(name, imgId);
entries.push_back(entry);
}
}
}
void clTernServer::OnError(const wxString& why)
{
m_workerThread->Stop();
wxDELETE(m_workerThread);
CL_ERROR("[WebTools] %s", why);
}
void clTernServer::OnTernWorkerThreadDone(const clTernWorkerThread::Reply& reply)
{
m_workerThread->Stop();
wxDELETE(m_workerThread);
RecycleIfNeeded();
m_entries.clear();
CL_DEBUGS(reply.json);
switch(reply.requestType) {
case clTernWorkerThread::kFunctionTip:
m_jsCCManager->OnFunctionTipReady(ProcessCalltip(reply.json), reply.filename);
break;
case clTernWorkerThread::kCodeCompletion:
ProcessOutput(reply.json, m_entries);
m_jsCCManager->OnCodeCompleteReady(m_entries, reply.filename);
break;
case clTernWorkerThread::kFindDefinition: {
clTernDefinition loc;
if(ProcessDefinitionOutput(reply.json, loc)) { m_jsCCManager->OnDefinitionFound(loc); }
} break;
case clTernWorkerThread::kReparse: {
// TODO ??
} break;
case clTernWorkerThread::kReset:
// TODO ??
break;
}
}
clCallTipPtr clTernServer::ProcessCalltip(const wxString& output)
{
// Function calltip response:
// ================================
// {
// "type": "fn(f: fn(elt: ?, i: number), context?: ?)",
// "name": "Array.prototype.forEach",
// "exprName": "forEach",
// "url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach",
// "doc": "Executes a provided function once per array element.",
// "origin": "ecma5"
// }
TagEntryPtrVector_t tags;
TagEntryPtr t(new TagEntry());
JSON root(output);
wxString type = root.toElement().namedObject("type").toString();
int imgID;
wxString sig, retValue;
ProcessType(type, sig, retValue, imgID);
if(sig.IsEmpty()) { return NULL; }
t->SetSignature(sig);
t->SetReturnValue(retValue);
t->SetKind("function");
t->SetFlags(TagEntry::Tag_No_Signature_Format);
tags.push_back(t);
return new clCallTip(tags);
}
JSONItem clTernServer::CreateLocation(wxStyledTextCtrl* ctrl, int pos)
{
if(pos == wxNOT_FOUND) { pos = ctrl->GetCurrentPos(); }
int lineNo = ctrl->LineFromPosition(pos);
JSONItem loc = JSONItem::createObject("end");
loc.addProperty("line", lineNo);
// Pass the column
int lineStartPos = ctrl->PositionFromLine(lineNo);
pos = pos - lineStartPos;
loc.addProperty("ch", pos);
return loc;
}
bool clTernServer::PostFunctionTipRequest(IEditor* editor, int pos)
{
// Sanity
if(m_workerThread) return false; // another request is in progress
if(m_port == wxNOT_FOUND) return false; // don't know tern's port
++m_recycleCount;
wxStyledTextCtrl* ctrl = editor->GetCtrl();
// Write the modified buffer into a file
// wxFileName tmpFileName = wxFileName::CreateTempFileName("tern");
// if(!FileUtils::WriteFileContent(tmpFileName, ctrl->GetText())) return false;
// Prepare the request
JSON root(cJSON_Object);
JSONItem query = JSONItem::createObject("query");
root.toElement().append(query);
query.addProperty("type", wxString("type"));
query.addProperty("file", wxString("#0"));
query.append(CreateLocation(ctrl, pos));
// Creae the files array
JSONItem files = CreateFilesArray(editor);
root.toElement().append(files);
clTernWorkerThread::Request* req = new clTernWorkerThread::Request;
req->jsonRequest = root.toElement().FormatRawString();
req->filename = editor->GetFileName().GetFullPath();
req->type = clTernWorkerThread::kFunctionTip;
// Create the worker thread and start the request
m_workerThread = new clTernWorkerThread(this);
m_workerThread->Start();
m_workerThread->Add(req);
return true;
}
JSONItem clTernServer::CreateFilesArray(IEditor* editor, bool forDelete)
{
const wxString fileContent = editor->GetCtrl()->GetText();
JSONItem files = JSONItem::createArray("files");
JSONItem file = JSONItem::createObject();
files.arrayAppend(file);
wxString filename;
if(!m_workingDirectory.IsEmpty()) {
wxFileName fn(editor->GetFileName());
fn.MakeRelativeTo(m_workingDirectory);
filename = fn.GetFullPath();
} else {
filename = editor->GetFileName().GetFullName();
}
if(forDelete) {
file.addProperty("type", wxString("delete"));
file.addProperty("name", filename);
} else {
file.addProperty("type", wxString("full"));
file.addProperty("name", filename);
file.addProperty("text", fileContent);
}
return files;
}
bool clTernServer::LocateNodeJS(wxFileName& nodeJS)
{
nodeJS = clNodeJS::Get().GetNode();
return nodeJS.IsOk() && nodeJS.FileExists();
}
void clTernServer::ClearFatalErrorFlag() { m_fatalError = false; }
bool clTernServer::PostFindDefinitionRequest(IEditor* editor)
{
// Sanity
if(m_workerThread) return false; // another request is in progress
if(m_port == wxNOT_FOUND) return false; // don't know tern's port
++m_recycleCount;
wxStyledTextCtrl* ctrl = editor->GetCtrl();
// Prepare the request
JSON root(cJSON_Object);
JSONItem query = JSONItem::createObject("query");
root.toElement().append(query);
query.addProperty("type", wxString("definition"));
query.addProperty("file", wxString("#0"));
query.append(CreateLocation(ctrl));
// Creae the files array
JSONItem files = CreateFilesArray(editor);
root.toElement().append(files);
clTernWorkerThread::Request* req = new clTernWorkerThread::Request;
req->jsonRequest = root.toElement().FormatRawString();
req->filename = editor->GetFileName().GetFullPath();
req->type = clTernWorkerThread::kFindDefinition;
// Create the worker thread and start the request
m_workerThread = new clTernWorkerThread(this);
m_workerThread->Start();
m_workerThread->Add(req);
return true;
}
bool clTernServer::ProcessDefinitionOutput(const wxString& output, clTernDefinition& loc)
{
JSON root(output);
JSONItem json = root.toElement();
if(json.hasNamedObject("file")) {
wxFileName fn(json.namedObject("file").toString());
if(!m_workingDirectory.IsEmpty()) { fn.MakeAbsolute(m_workingDirectory); }
loc.file = fn.GetFullPath();
loc.start = json.namedObject("start").toInt();
loc.end = json.namedObject("end").toInt();
return true;
} else if(json.hasNamedObject("url")) {
loc.url = json.namedObject("url").toString();
return true;
}
return false;
}
bool clTernServer::PostResetCommand(bool forgetFiles)
{
// Sanity
if(m_workerThread) return false; // another request is in progress
if(m_port == wxNOT_FOUND) return false; // don't know tern's port
++m_recycleCount;
// Prepare the request
JSON root(cJSON_Object);
JSONItem query = JSONItem::createObject("query");
root.toElement().append(query);
query.addProperty("type", wxString("reset"));
if(forgetFiles) { query.addProperty("forgetFiles", true); }
clTernWorkerThread::Request* req = new clTernWorkerThread::Request;
req->jsonRequest = root.toElement().FormatRawString();
req->type = clTernWorkerThread::kReset;
// Create the worker thread and start the request
m_workerThread = new clTernWorkerThread(this);
m_workerThread->Start();
m_workerThread->Add(req);
return true;
}
bool clTernServer::PostReparseCommand(IEditor* editor)
{
// Sanity
if(!editor) return false;
if(m_workerThread) return false; // another request is in progress
if(m_port == wxNOT_FOUND) return false; // don't know tern's port
++m_recycleCount;
// Prepare the request
JSON root(cJSON_Object);
JSONItem files = CreateFilesArray(editor);
root.toElement().append(files);
clTernWorkerThread::Request* req = new clTernWorkerThread::Request;
req->jsonRequest = root.toElement().FormatRawString();
req->type = clTernWorkerThread::kReparse;
// Create the worker thread and start the request
m_workerThread = new clTernWorkerThread(this);
m_workerThread->Start();
m_workerThread->Add(req);
return true;
}
You can’t perform that action at this time.