/* * Copyright (C) 2024-2025 Petr Mironychev * Copyright (C) 2025 Povilas Kanapickas * * This file is part of QodeAssist. * * QodeAssist 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. * * QodeAssist 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 QodeAssist. If not, see . */ #include "CodeHandler.hpp" #include #include #include namespace QodeAssist { struct LanguageProperties { QString name; QString commentStyle; QVector namesFromModel; QVector fileExtensions; }; const QVector customLanguagesFromSettings() { QVector customLanguages; const QStringList customLanguagesList = Settings::codeCompletionSettings().customLanguages(); for (const QString &entry : customLanguagesList) { if (entry.trimmed().isEmpty()) { continue; } QStringList parts = entry.split(','); if (parts.size() < 4) { continue; } QString name = parts[0].trimmed(); QString commentStyle = parts[1].trimmed(); QStringList modelNamesList = parts[2].trimmed().split(' ', Qt::SkipEmptyParts); QStringList extensionsList = parts[3].trimmed().split(' ', Qt::SkipEmptyParts); if (!name.isEmpty() && !commentStyle.isEmpty() && !modelNamesList.isEmpty() && !extensionsList.isEmpty()) { QVector modelNames; for (const auto &modelName : modelNamesList) { modelNames.append(modelName); } QVector extensions; for (const auto &ext : extensionsList) { extensions.append(ext); } customLanguages.append({name, commentStyle, modelNames, extensions}); } } return customLanguages; } const QVector &getKnownLanguages() { static QVector knownLanguages = { {"python", "#", {"python", "py"}, {"py"}}, {"lua", "--", {"lua"}, {"lua"}}, {"js", "//", {"js", "javascript"}, {"js", "jsx"}}, {"ts", "//", {"ts", "typescript"}, {"ts", "tsx"}}, {"c-like", "//", {"c", "c++", "cpp"}, {"c", "h", "cpp", "hpp"}}, {"java", "//", {"java"}, {"java"}}, {"c#", "//", {"cs", "csharp"}, {"cs"}}, {"php", "//", {"php"}, {"php"}}, {"ruby", "#", {"rb", "ruby"}, {"rb"}}, {"go", "//", {"go"}, {"go"}}, {"swift", "//", {"swift"}, {"swift"}}, {"kotlin", "//", {"kt", "kotlin"}, {"kt", "kotlin"}}, {"scala", "//", {"scala"}, {"scala"}}, {"r", "#", {"r"}, {"r"}}, {"shell", "#", {"shell", "bash", "sh"}, {"sh", "bash"}}, {"perl", "#", {"pl", "perl"}, {"pl"}}, {"hs", "--", {"hs", "haskell"}, {"hs"}}, {"qml", "//", {"qml"}, {"qml"}}, }; knownLanguages.append(customLanguagesFromSettings()); return knownLanguages; } static QHash buildLanguageToCommentPrefixMap() { QHash result; for (const auto &languageProps : getKnownLanguages()) { result[languageProps.name] = languageProps.commentStyle; } return result; } static QHash buildExtensionToLanguageMap() { QHash result; for (const auto &languageProps : getKnownLanguages()) { for (const auto &extension : languageProps.fileExtensions) { result[extension] = languageProps.name; } } return result; } static QHash buildModelLanguageNameToLanguageMap() { QHash result; for (const auto &languageProps : getKnownLanguages()) { for (const auto &nameFromModel : languageProps.namesFromModel) { result[nameFromModel] = languageProps.name; } } return result; } QString CodeHandler::processText(QString text, QString currentFilePath) { QString result; QStringList lines = text.split('\n'); bool inCodeBlock = false; QString pendingComments; auto currentFileExtension = QFileInfo(currentFilePath).suffix(); auto currentLanguage = detectLanguageFromExtension(currentFileExtension); auto addPendingCommentsIfAny = [&]() { if (pendingComments.isEmpty()) { return; } QStringList commentLines = pendingComments.split('\n'); QString commentPrefix = getCommentPrefix(currentLanguage); for (const QString &commentLine : commentLines) { if (!commentLine.trimmed().isEmpty()) { result += commentPrefix + " " + commentLine.trimmed() + "\n"; } else { result += "\n"; } } pendingComments.clear(); }; for (const QString &line : lines) { if (line.trimmed().startsWith("```")) { if (!inCodeBlock) { auto lineLanguage = detectLanguageFromLine(line); if (!lineLanguage.isEmpty()) { currentLanguage = lineLanguage; } addPendingCommentsIfAny(); if (lineLanguage.isEmpty()) { // language not detected, so add direct output from model, if any result += line.trimmed().mid(3) + "\n"; // add the remainder of line after ``` } } inCodeBlock = !inCodeBlock; continue; } if (inCodeBlock) { result += line + "\n"; } else { QString trimmed = line.trimmed(); if (!trimmed.isEmpty()) { pendingComments += trimmed + "\n"; } else { pendingComments += "\n"; } } } addPendingCommentsIfAny(); return result; } QString CodeHandler::getCommentPrefix(const QString &language) { static const auto commentPrefixes = buildLanguageToCommentPrefixMap(); return commentPrefixes.value(language, "//"); } QString CodeHandler::detectLanguageFromLine(const QString &line) { static const auto modelNameToLanguage = buildModelLanguageNameToLanguageMap(); return modelNameToLanguage.value(line.trimmed().mid(3).trimmed(), ""); } QString CodeHandler::detectLanguageFromExtension(const QString &extension) { static const auto extensionToLanguage = buildExtensionToLanguageMap(); return extensionToLanguage.value(extension.toLower(), ""); } const QRegularExpression &CodeHandler::getFullCodeBlockRegex() { static const QRegularExpression regex(R"(```[\w\s]*\n([\s\S]*?)```)", QRegularExpression::MultilineOption); return regex; } const QRegularExpression &CodeHandler::getPartialStartBlockRegex() { static const QRegularExpression regex(R"(```[\w\s]*\n([\s\S]*?)$)", QRegularExpression::MultilineOption); return regex; } const QRegularExpression &CodeHandler::getPartialEndBlockRegex() { static const QRegularExpression regex(R"(^([\s\S]*?)```)", QRegularExpression::MultilineOption); return regex; } } // namespace QodeAssist