diff --git a/duchain/builders/contextbuilder.cpp b/duchain/builders/contextbuilder.cpp index 31de9f7..062d08a 100644 --- a/duchain/builders/contextbuilder.cpp +++ b/duchain/builders/contextbuilder.cpp @@ -1,326 +1,327 @@ /* This file is part of KDevelop * * Copyright 2010 Niko Sams * Copyright 2010 Alexander Dymo * Copyright (C) 2011-2015 Miquel Sabaté Solà * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include +#include #include #include #include #include #include Q_LOGGING_CATEGORY(DUCHAIN, "kdevelop.languages.ruby.duchain") using namespace KDevelop; using namespace ruby; ContextBuilder::ContextBuilder() { m_mapAst = false; } ContextBuilder::~ContextBuilder() { } ReferencedTopDUContext ContextBuilder::build(const IndexedString &url, Ast *node, ReferencedTopDUContext updateContext) { if (!updateContext) { DUChainReadLocker lock; updateContext = DUChain::self()->chainForDocument(url); } if (updateContext) { rDebug() << "Re-compiling" << url.str(); DUChainWriteLocker lock; updateContext->clearImportedParentContexts(); updateContext->parsingEnvironmentFile()->clearModificationRevisions(); updateContext->clearProblems(); updateContext->updateImportsCache(); } else { rDebug() << "Compiling"; } auto top = ContextBuilderBase::build(url, node, updateContext); { DUChainWriteLocker lock(DUChain::lock()); top->updateImportsCache(); } return top; } void ContextBuilder::setEditor(EditorIntegrator *editor) { m_editor = editor; } EditorIntegrator * ContextBuilder::editor() const { return m_editor; } void ContextBuilder::setContextOnNode(Ast *node, KDevelop::DUContext *ctx) { if (node->tree) { node->tree->context = ctx; } node->context = ctx; } KDevelop::DUContext * ContextBuilder::contextFromNode(Ast *node) { if (node->tree) { DUContext *ctx = static_cast(node->tree->context); return ctx; } return node->context; } DUContext * ContextBuilder::newContext(const RangeInRevision &range) { return new RubyNormalDUContext(range, currentContext()); } KDevelop::TopDUContext * ContextBuilder::newTopContext(const RangeInRevision &range, ParsingEnvironmentFile *file) { KDevelop::IndexedString doc(m_editor->url()); if (!file) { file = new KDevelop::ParsingEnvironmentFile(doc); file->setLanguage(KDevelop::IndexedString("Ruby")); } TopDUContext *top = new RubyDUContext(doc, range, file); top->setType(DUContext::Global); return top; } const KDevelop::CursorInRevision ContextBuilder::startPos(const Ast *node) const { return m_editor->findPosition(node->tree, Edge::FrontEdge); } KDevelop::RangeInRevision ContextBuilder::editorFindRange(Ast *fromRange, Ast *toRange) { return m_editor->findRange(fromRange->tree, toRange->tree); } const DocumentRange ContextBuilder::getDocumentRange(const Node *node) const { KTextEditor::Range range(node->pos.start_line - 1, node->pos.start_col, node->pos.end_line - 1, node->pos.end_col); return DocumentRange(m_editor->url(), range); } const DocumentRange ContextBuilder::getDocumentRange(const RangeInRevision &range) const { return DocumentRange(m_editor->url(), range.castToSimpleRange()); } KDevelop::QualifiedIdentifier ContextBuilder::identifierForNode(NameAst *name) { if (!name) { return QualifiedIdentifier(); } return QualifiedIdentifier(name->value); } void ContextBuilder::startVisiting(Ast *node) { const auto &builtins = builtinsFile(); if (compilingContexts()) { TopDUContext *top = dynamic_cast(currentContext()); { // Mark that we will use a cached import-structure. DUChainWriteLocker lock; top->updateImportsCache(); } Q_ASSERT(top); bool hasImports; { DUChainReadLocker rlock; hasImports = !top->importedParentContexts().isEmpty(); } if (!hasImports && top->url() != builtins) { DUChainWriteLocker wlock; TopDUContext *import = DUChain::self()->chainForDocument(builtins); if (!import) { rDebug() << "importing the builtins file failed"; Q_ASSERT(false); } else { top->addImportedParentContext(import); top->updateImportsCache(); } } } AstVisitor::visitCode(node); } void ContextBuilder::visitModuleStatement(Ast *node) { if (!node->foundProblems) { AstVisitor::visitModuleStatement(node); } } void ContextBuilder::visitClassStatement(Ast *node) { if (!node->foundProblems) { AstVisitor::visitClassStatement(node); } } void ContextBuilder::visitMethodStatement(Ast *node) { Node *aux = node->tree; NameAst name(node); DUContext *params = nullptr; node->tree = aux->r; if (node->tree) { RangeInRevision range = rangeForMethodArguments(node); params = openContext(node, range, DUContext::Function, &name); visitMethodArguments(node); closeContext(); } node->tree = aux->l; DUContext *body = openContext(node, DUContext::Other, &name); if (compilingContexts()) { DUChainWriteLocker wlock; if (params) { body->addImportedParentContext(params); } body->setInSymbolTable(false); } visitBody(node); closeContext(); node->tree = aux; } void ContextBuilder::visitBlock(Ast *node) { if (!node->tree) { return; } DUContext *block = openContext(node, DUContext::Other); if (compilingContexts()) { DUChainWriteLocker wlock; block->setInSymbolTable(false); } AstVisitor::visitBlock(node); closeContext(); } void ContextBuilder::visitRequire(Ast *node, bool relative) { AstVisitor::visitRequire(node); Node *aux = node->tree->r; /* If this is not a string, don't even care about it. */ if (aux->kind != token_string) { return; } Path path = Loader::getRequiredFile(aux, m_editor, relative); if (!path.isValid()) { appendProblem( aux, i18n("LoadError: cannot load such file: %1", path.path()), IProblem::Warning ); return; } require(path); } void ContextBuilder::require(const KDevelop::Path &path) { DUChainWriteLocker lock; const IndexedString idx(path.path()); ReferencedTopDUContext ctx = DUChain::self()->chainForDocument(idx); if (!ctx) { /* * Schedule the required file for parsing, and schedule the current one * for reparsing after that is done. */ m_unresolvedImports.append(idx); auto parser = ICore::self()->languageController()->backgroundParser(); if (parser->isQueued(idx)) { parser->removeDocument(idx); } parser->addDocument(idx, TopDUContext::ForceUpdate, m_priority - 1, nullptr, ParseJob::FullSequentialProcessing); } else { currentContext()->addImportedParentContext(ctx); } } void ContextBuilder::appendProblem(const Node *node, const QString &msg, IProblem::Severity sev) { ProblemPointer p(new Problem()); p->setFinalLocation(getDocumentRange(node)); p->setSource(IProblem::SemanticAnalysis); p->setDescription(msg); p->setSeverity(sev); { DUChainWriteLocker lock; topContext()->addProblem(p); } } void ContextBuilder::appendProblem(const RangeInRevision &range, const QString &msg, IProblem::Severity sev) { ProblemPointer p(new Problem()); p->setFinalLocation(getDocumentRange(range)); p->setSource(IProblem::SemanticAnalysis); p->setDescription(msg); p->setSeverity(sev); { DUChainWriteLocker lock; topContext()->addProblem(p); } } const RangeInRevision ContextBuilder::rangeForMethodArguments(Ast *node) { if (!node->tree) { return RangeInRevision(); } Ast last(get_last_expr(node->tree), node->context); return editorFindRange(node, &last); } diff --git a/launcher.cpp b/launcher.cpp index 175f196..82488eb 100644 --- a/launcher.cpp +++ b/launcher.cpp @@ -1,224 +1,225 @@ /* This file is part of KDevelop * * Copyright (C) 2014-2015 Miquel Sabaté Solà * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ruby; using namespace KDevelop; namespace { /** * @internal Set up the launch configuration before the run occurs. * @param cfg the KConfigGroup for this launch. * @param document the currently active document. */ void setupBeforeRun(KConfigGroup &cfg, IDocument *document) { Path root = rails::Helpers::findRailsRoot(document->url()); if (root.isValid()) { cfg.writeEntry("Working Directory", root.toLocalFile()); } else { cfg.writeEntry("Working Directory", root.parent().path()); } } /** * @internal Find the method under the cursor in the given \p doc. It's * used by the runCurrentTestFunction() slot. */ QString findFunctionUnderCursor(KDevelop::IDocument *doc) { DUChainReadLocker lock; const auto top = DUChainUtils::standardContextForUrl(doc->url()); if (!top) { return ""; } const auto &cursor = doc->cursorPosition(); auto context = top->findContextAt(CursorInRevision( cursor.line(), cursor.column() )); if (!context) { return ""; } rDebug() << "CONTEXT ID" << context->localScopeIdentifier(); return context->localScopeIdentifier().toString(); } } Launcher::Launcher(LanguageSupport *support) : QObject(support) , m_file(nullptr) , m_function(nullptr) { m_support = support; } void Launcher::setupActions(KActionCollection &actions) { QAction *action = actions.addAction("ruby_run_current_file"); action->setText(i18n("Run Current File")); action->setShortcut(Qt::META | Qt::Key_F9); connect(action, &QAction::triggered, this, &Launcher::runCurrentFile); action = actions.addAction("ruby_run_current_test_function"); action->setText(i18n("Run Current Test Function")); action->setShortcut(Qt::META | Qt::SHIFT | Qt::Key_F9); connect(action, &QAction::triggered, this, &Launcher::runCurrentTest); } ILaunchConfiguration * Launcher::launchConfiguration(const QString &name) { for (auto config : m_support->core()->runController()->launchConfigurations()) { if (config->name() == name) { return config; } } auto executePlugin = m_support->core()->pluginController()-> pluginForExtension("org.kdevelop.IExecutePlugin")->extension(); auto type = m_support->core()->runController()->launchConfigurationTypeForId( executePlugin->nativeAppConfigTypeId()); if (!type) { return nullptr; } auto mode = m_support->core()->runController()->launchModeForId("execute"); if (!mode) { return nullptr; } KDevelop::ILauncher *launcher = nullptr; for (auto l : type->launchers()) { if (l->supportedModes().contains(QStringLiteral("execute"))) { launcher = l; break; } } if (!launcher) { return nullptr; } auto config = m_support->core()->runController()->createLaunchConfiguration( type, qMakePair(mode->id(), launcher->id()), nullptr, name); KConfigGroup cfg = config->config(); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", "ruby"); cfg.sync(); return config; } void Launcher::runCurrentFile() { auto doc = ICore::self()->documentController()->activeDocument(); if (!doc) { return; } // Get out if this is not a Ruby file. if (!ICore::self()->languageController()->languagesForUrl(doc->url()). contains(m_support)) { return; } if (!m_file) { m_file = launchConfiguration(i18n("Current Ruby File")); if (!m_file) { return; } } KConfigGroup cfg = m_file->config(); setupBeforeRun(cfg, doc); cfg.writeEntry("Arguments", QStringList() << doc->url().toLocalFile()); cfg.sync(); m_support->core()->runController()-> execute(QStringLiteral("execute"), m_file); } void Launcher::runCurrentTest() { auto doc = ICore::self()->documentController()->activeDocument(); if (!doc) { return; } // Get out if this is not a Ruby file. if (!ICore::self()->languageController()->languagesForUrl(doc->url()). contains(m_support)) { return; } if (!m_function) { m_function = launchConfiguration(i18n("Current Ruby Test Function")); if (!m_function) { return; } } // Find function under the cursor (if any). const QString &¤tFunction = findFunctionUnderCursor(doc); if (currentFunction.isEmpty()) { return; } KConfigGroup cfg = m_function->config(); setupBeforeRun(cfg, doc); QStringList args{ doc->url().toLocalFile(), "-n", currentFunction }; cfg.writeEntry("Arguments", args.join(" ")); cfg.sync(); m_support->core()->runController()->execute( QStringLiteral("execute"), m_function); } diff --git a/parser/parser.cpp b/parser/parser.cpp index 4f015b9..57210db 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -1,122 +1,124 @@ /* This file is part of KDevelop * * Copyright (C) 2011-2015 Miquel Sabaté Solà * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include +#include + using namespace KDevelop; namespace ruby { Parser::Parser() { m_contents = nullptr; m_version = ruby21; ast = nullptr; } Parser::Parser(const IndexedString &fileName, const QByteArray &contents) { currentDocument = fileName; m_contents = contents; m_version = ruby21; ast = nullptr; } Parser::~Parser() { if (ast) { free_ast(ast->tree); delete ast; ast = nullptr; } } void Parser::setContents(const QByteArray &contents) { m_contents = contents; } void Parser::setRubyVersion(enum ruby_version version) { m_version = version; } Ast * Parser::parse() { struct options_t opts; struct error_t *aux; opts.path = currentDocument.str().toUtf8(); opts.contents = m_contents.data(); opts.version = m_version; // Let's call the parser ;) struct ast_t *res = rb_compile_file(&opts); ast = new Ast(res->tree); if (res->unrecoverable) { for (aux = res->errors; aux; aux = aux->next) { appendProblem(aux); } delete ast; ast = nullptr; rb_free(res); return nullptr; } else { problems.clear(); for (aux = res->errors; aux; aux = aux->next) { appendProblem(aux); } free_errors(res); free(res); } return ast; } void Parser::mapAstUse(Ast *node, const SimpleUse &use) { Q_UNUSED(node); Q_UNUSED(use); } const QString Parser::symbol(const Node *node) const { int len = node->pos.end_col - node->pos.start_col; return m_contents.mid(node->pos.offset - len, len); } void Parser::appendProblem(const struct error_t *error) { int col = (error->column > 0) ? error->column - 1 : 0; ProblemPointer problem(new KDevelop::Problem); KTextEditor::Cursor cursor(error->line - 1, col); KTextEditor::Range range(cursor, cursor); DocumentRange location(currentDocument, range); problem->setFinalLocation(location); problem->setDescription(QString(error->msg)); problem->setSource(IProblem::Parser); if (error->warning) { problem->setSeverity(IProblem::Error); } else { problem->setSeverity(IProblem::Warning); } problems << problem; } }