File indexing completed on 2024-05-05 04:40:55
0001 /* 0002 SPDX-FileCopyrightText: 2018 Amish K. Naidu <amhndu@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "scratchpad.h" 0008 #include "scratchpadview.h" 0009 #include "scratchpadjob.h" 0010 0011 #include <debug.h> 0012 0013 #include <interfaces/icore.h> 0014 #include <interfaces/iuicontroller.h> 0015 #include <interfaces/idocumentcontroller.h> 0016 #include <interfaces/iruncontroller.h> 0017 0018 #include <KPluginFactory> 0019 #include <KLocalizedString> 0020 #include <KSharedConfig> 0021 #include <KConfigGroup> 0022 #include <KActionCollection> 0023 0024 #include <QStandardItemModel> 0025 #include <QStandardPaths> 0026 #include <QDir> 0027 #include <QFileIconProvider> 0028 #include <QHash> 0029 0030 #include <algorithm> 0031 0032 K_PLUGIN_FACTORY_WITH_JSON(ScratchpadFactory, "scratchpad.json", registerPlugin<Scratchpad>(); ) 0033 0034 class ScratchpadToolViewFactory 0035 : public KDevelop::IToolViewFactory 0036 { 0037 public: 0038 explicit ScratchpadToolViewFactory(Scratchpad* plugin) 0039 : m_plugin(plugin) 0040 {} 0041 0042 QWidget* create(QWidget* parent = nullptr) override 0043 { 0044 return new ScratchpadView(parent, m_plugin); 0045 } 0046 0047 Qt::DockWidgetArea defaultPosition() const override 0048 { 0049 return Qt::LeftDockWidgetArea; 0050 } 0051 0052 QString id() const override 0053 { 0054 return QStringLiteral("org.kdevelop.ScratchpadView"); 0055 } 0056 0057 private: 0058 Scratchpad* const m_plugin; 0059 }; 0060 0061 namespace { 0062 KConfigGroup scratchCommands() 0063 { 0064 return KSharedConfig::openConfig()->group("Scratchpad").group("Commands"); 0065 } 0066 0067 KConfigGroup mimeCommands() 0068 { 0069 return KSharedConfig::openConfig()->group("Scratchpad").group("Mime Commands"); 0070 } 0071 0072 QString commandForScratch(const QFileInfo& file) 0073 { 0074 if (scratchCommands().hasKey(file.fileName())) { 0075 return scratchCommands().readEntry(file.fileName()); 0076 } 0077 0078 const auto suffix = file.suffix(); 0079 if (mimeCommands().hasKey(suffix)) { 0080 return mimeCommands().readEntry(suffix); 0081 } 0082 0083 const static QHash<QString, QString> defaultCommands = { 0084 {QStringLiteral("cpp"), QStringLiteral("g++ -std=c++11 -o /tmp/a.out $f && /tmp/a.out")}, 0085 {QStringLiteral("py"), QStringLiteral("python $f")}, 0086 {QStringLiteral("js"), QStringLiteral("node $f")}, 0087 {QStringLiteral("c"), QStringLiteral("gcc -o /tmp/a.out $f && /tmp/a.out")}, 0088 }; 0089 0090 return defaultCommands.value(suffix); 0091 } 0092 } 0093 0094 Scratchpad::Scratchpad(QObject* parent, const QVariantList& args) 0095 : KDevelop::IPlugin(QStringLiteral("scratchpad"), parent) 0096 , m_factory(new ScratchpadToolViewFactory(this)) 0097 , m_model(new QStandardItemModel(this)) 0098 , m_runAction(new QAction(this)) 0099 { 0100 Q_UNUSED(args); 0101 0102 qCDebug(PLUGIN_SCRATCHPAD) << "Scratchpad plugin is loaded!"; 0103 0104 core()->uiController()->addToolView(i18nc("@title:window", "Scratchpad"), m_factory); 0105 0106 const QDir dataDir(dataDirectory()); 0107 if (!dataDir.exists()) { 0108 qCDebug(PLUGIN_SCRATCHPAD) << "Creating directory" << dataDir; 0109 dataDir.mkpath(QStringLiteral(".")); 0110 } 0111 0112 const QFileInfoList scratches = dataDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot); 0113 0114 for (const auto& fileInfo : scratches) { 0115 addFileToModel(fileInfo); 0116 0117 // TODO if scratch is open (happens when restarting), set pretty name, below code doesn't work 0118 // auto* document = core()->documentController()->documentForUrl(QUrl::fromLocalFile(fileInfo.absoluteFilePath())); 0119 // if (document) { 0120 // document->setPrettyName(i18n("scratch:%1", fileInfo.fileName())); 0121 // } 0122 } 0123 } 0124 0125 QStandardItemModel* Scratchpad::model() const 0126 { 0127 return m_model; 0128 } 0129 0130 QString Scratchpad::dataDirectory() 0131 { 0132 const static QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) 0133 + QLatin1String("/kdevscratchpad/scratches/"); 0134 return dir; 0135 } 0136 0137 void Scratchpad::openScratch(const QModelIndex& index) 0138 { 0139 const QUrl scratchUrl = QUrl::fromLocalFile(index.data(FullPathRole).toString()); 0140 auto* const document = core()->documentController()->openDocument(scratchUrl); 0141 document->setPrettyName(i18nc("prefix to distinguish scratch tabs", "scratch:%1", index.data().toString())); 0142 } 0143 0144 void Scratchpad::runScratch(const QModelIndex& index) 0145 { 0146 qCDebug(PLUGIN_SCRATCHPAD) << "run" << index.data().toString(); 0147 0148 auto command = index.data(RunCommandRole).toString(); 0149 command.replace(QLatin1String("$f"), index.data(FullPathRole).toString()); 0150 if (!command.isEmpty()) { 0151 auto* job = new ScratchpadJob(command, index.data().toString(), this); 0152 core()->runController()->registerJob(job); 0153 } 0154 } 0155 0156 void Scratchpad::removeScratch(const QModelIndex& index) 0157 { 0158 const QString path = index.data(FullPathRole).toString(); 0159 if (auto* document = core()->documentController()->documentForUrl(QUrl::fromLocalFile(path))) { 0160 document->close(); 0161 } 0162 0163 if (QFile::remove(path)) { 0164 qCDebug(PLUGIN_SCRATCHPAD) << "removed" << index.data(FullPathRole); 0165 scratchCommands().deleteEntry(index.data().toString()); 0166 m_model->removeRow(index.row()); 0167 } else { 0168 emit actionFailed(i18n("Failed to remove scratch: %1", index.data().toString())); 0169 } 0170 } 0171 0172 void Scratchpad::createScratch(const QString& name) 0173 { 0174 if (!m_model->findItems(name).isEmpty()) { 0175 emit actionFailed(i18n("Failed to create scratch: Name already in use")); 0176 return; 0177 } 0178 0179 QFile file(dataDirectory() + name); 0180 if (!file.exists() && file.open(QIODevice::WriteOnly)) { // create a new file if it doesn't exist 0181 file.close(); 0182 } 0183 0184 if (file.exists()) { 0185 addFileToModel(file); 0186 } else { 0187 emit actionFailed(i18n("Failed to create new scratch")); 0188 } 0189 } 0190 0191 void Scratchpad::renameScratch(const QModelIndex& index, const QString& previousName) 0192 { 0193 const QString newName = index.data().toString(); 0194 if (newName.contains(QDir::separator())) { 0195 m_model->setData(index, previousName); // undo 0196 emit actionFailed(i18n("Failed to rename scratch: Names must not include path separator")); 0197 return; 0198 } 0199 0200 const QString previousPath = dataDirectory() + previousName; 0201 const QString newPath = dataDirectory() + index.data().toString(); 0202 if (previousPath == newPath) { 0203 return; 0204 } 0205 0206 if (QFile::rename(previousPath, newPath)) { 0207 qCDebug(PLUGIN_SCRATCHPAD) << "renamed" << previousPath << "to" << newPath; 0208 0209 m_model->setData(index, newPath, Scratchpad::FullPathRole); 0210 m_model->itemFromIndex(index)->setIcon(m_iconProvider.icon(QFileInfo(newPath))); 0211 auto config = scratchCommands(); 0212 config.deleteEntry(previousName); 0213 config.writeEntry(newName, index.data(Scratchpad::RunCommandRole)); 0214 0215 // close old and re-open the closed document 0216 if (auto* document = core()->documentController()->documentForUrl(QUrl::fromLocalFile(previousPath))) { 0217 // FIXME is there a better way ? this feels hacky 0218 document->close(); 0219 document = core()->documentController()->openDocument(QUrl::fromLocalFile(newPath)); 0220 document->setPrettyName(i18nc("prefix to distinguish scratch tabs", "scratch:%1", index.data().toString())); 0221 } 0222 } else { 0223 qCWarning(PLUGIN_SCRATCHPAD) << "failed renaming" << previousPath << "to" << newPath; 0224 // rollback 0225 m_model->setData(index, previousName); 0226 emit actionFailed(i18n("Failed renaming scratch.")); 0227 } 0228 } 0229 0230 void Scratchpad::addFileToModel(const QFileInfo& fileInfo) 0231 { 0232 auto* const item = new QStandardItem(m_iconProvider.icon(fileInfo), fileInfo.fileName()); 0233 item->setData(fileInfo.absoluteFilePath(), FullPathRole); 0234 const auto command = commandForScratch(fileInfo); 0235 item->setData(command, RunCommandRole); 0236 scratchCommands().writeEntry(item->text(), item->data(RunCommandRole)); 0237 m_model->appendRow(item); 0238 } 0239 0240 void Scratchpad::setCommand(const QModelIndex& index, const QString& command) 0241 { 0242 qCDebug(PLUGIN_SCRATCHPAD) << "set command" << index.data(); 0243 m_model->setData(index, command, RunCommandRole); 0244 scratchCommands().writeEntry(index.data().toString(), command); 0245 0246 mimeCommands().writeEntry(QFileInfo(index.data().toString()).suffix(), command); 0247 } 0248 0249 QAction* Scratchpad::runAction() const 0250 { 0251 return m_runAction; 0252 } 0253 0254 void Scratchpad::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) 0255 { 0256 Q_UNUSED(window); 0257 0258 xmlFile = QStringLiteral("kdevscratchpad.rc"); 0259 0260 // add to gui action collection, so that the shorcut is easily configurable 0261 // action setup done in ScratchpadView 0262 actions.addAction(QStringLiteral("run_scratch"), m_runAction); 0263 } 0264 0265 0266 #include "scratchpad.moc" 0267 #include "moc_scratchpad.cpp"