namespace KDevelop
{
const QString SourceFormatterController::kateModeLineConfigKey = "ModelinesEnabled";
const QString SourceFormatterController::kateOverrideIndentationConfigKey = "OverrideKateIndentationMode";
const QString SourceFormatterController::styleCaptionKey = "Caption";
const QString SourceFormatterController::styleContentKey = "Content";
const QString SourceFormatterController::supportedMimeTypesKey = "X-KDevelop-SupportedMimeTypes";
SourceFormatterController::SourceFormatterController(QObject *parent)
: ISourceFormatterController(parent)
{
setObjectName("SourceFormatterController");
setComponentData(KComponentData("kdevsourceformatter"));
setXMLFile("kdevsourceformatter.rc");
if (Core::self()->setupFlags() & Core::NoUi) return;
m_formatTextAction = actionCollection()->addAction("edit_reformat_source");
m_formatTextAction->setText(i18n("&Reformat Source"));
m_formatTextAction->setToolTip(i18n("Reformat source using AStyle"));
m_formatTextAction->setWhatsThis(i18n("Reformat sourceSource reformatting "
"functionality using astyle library.
"));
connect(m_formatTextAction, SIGNAL(triggered()), this, SLOT(beautifySource()));
m_formatLine = actionCollection()->addAction("edit_reformat_line");
m_formatLine->setText(i18n("Reformat Line"));
m_formatLine->setToolTip(i18n("Reformat current line using AStyle"));
m_formatLine->setWhatsThis(i18n("Reformat line"
"Source reformatting of line under cursor using astyle library.
"));
connect(m_formatLine, SIGNAL(triggered()), this, SLOT(beautifyLine()));
m_formatFilesAction = actionCollection()->addAction("tools_astyle");
m_formatFilesAction->setText(i18n("Format files"));
m_formatFilesAction->setToolTip(i18n("Format file(s) using the current theme"));
m_formatFilesAction->setWhatsThis(i18n("Format filesFormatting functionality using astyle library.
"));
connect(m_formatFilesAction, SIGNAL(triggered()), this, SLOT(formatFiles()));
m_formatTextAction->setEnabled(false);
m_formatFilesAction->setEnabled(true);
connect(Core::self()->documentController(), SIGNAL(documentActivated(KDevelop::IDocument*)),
this, SLOT(activeDocumentChanged(KDevelop::IDocument*)));
connect(Core::self()->documentController(), SIGNAL(documentLoaded(KDevelop::IDocument*)),
this, SLOT(documentLoaded(KDevelop::IDocument*)));
activeDocumentChanged(Core::self()->documentController()->activeDocument());
}
void SourceFormatterController::documentLoaded( IDocument* doc )
{
KMimeType::Ptr mime = KMimeType::findByUrl(doc->url());
adaptEditorIndentationMode( doc, formatterForMimeType(mime) );
}
void SourceFormatterController::initialize()
{
}
SourceFormatterController::~SourceFormatterController()
{
}
ISourceFormatter* SourceFormatterController::formatterForUrl(const KUrl &url)
{
KMimeType::Ptr mime = KMimeType::findByUrl(url);
return formatterForMimeType(mime);
}
KConfigGroup SourceFormatterController::configuration()
{
return Core::self()->activeSession()->config()->group( "SourceFormatter" );
}
static ISourceFormatter* findFirstFormatterForMimeType( const KMimeType::Ptr& mime )
{
static QHash knownFormatters;
if (knownFormatters.contains(mime->name()))
return knownFormatters[mime->name()];
foreach( IPlugin* p, Core::self()->pluginController()->allPluginsForExtension( "org.kdevelop.ISourceFormatter" ) ) {
KPluginInfo info = Core::self()->pluginController()->pluginInfo( p );
if( info.property( SourceFormatterController::supportedMimeTypesKey ).toStringList().contains( mime->name() ) ) {
ISourceFormatter *formatter = p->extension();
knownFormatters[mime->name()] = formatter;
return formatter;
}
}
knownFormatters[mime->name()] = 0;
return 0;
}
ISourceFormatter* SourceFormatterController::formatterForMimeType(const KMimeType::Ptr &mime)
{
if( !isMimeTypeSupported( mime ) ) {
return 0;
}
QString formatter = configuration().readEntry( mime->name(), "" );
if( formatter.isEmpty() )
{
return findFirstFormatterForMimeType( mime );
}
QStringList formatterinfo = formatter.split( "||", QString::SkipEmptyParts );
if( formatterinfo.size() != 2 ) {
kDebug() << "Broken formatting entry for mime:" << mime << "current value:" << formatter;
return 0;
}
return Core::self()->pluginControllerInternal()->extensionForPlugin( "org.kdevelop.ISourceFormatter", formatterinfo.at(0) );
}
bool SourceFormatterController::isMimeTypeSupported(const KMimeType::Ptr &mime)
{
if( findFirstFormatterForMimeType( mime ) ) {
return true;
}
return false;
}
QString SourceFormatterController::indentationMode(const KMimeType::Ptr &mime)
{
if (mime->is("text/x-c++src") || mime->is("text/x-chdr") ||
mime->is("text/x-c++hdr") || mime->is("text/x-csrc") ||
mime->is("text/x-java") || mime->is("text/x-csharp"))
return "cstyle";
return "none";
}
QString SourceFormatterController::addModelineForCurrentLang(QString input, const KUrl& url, const KMimeType::Ptr& mime)
{
if( !isMimeTypeSupported(mime) )
return input;
QRegExp kateModelineWithNewline("\\s*\\n//\\s*kate:(.*)$");
// If there already is a modeline in the document, adapt it while formatting, even
// if "add modeline" is disabled.
if( !configuration().readEntry( SourceFormatterController::kateModeLineConfigKey, false ) &&
kateModelineWithNewline.indexIn( input ) == -1 )
return input;
ISourceFormatter* fmt = formatterForMimeType( mime );
ISourceFormatter::Indentation indentation = fmt->indentation(url);
if( !indentation.isValid() )
return input;
QString output;
QTextStream os(&output, QIODevice::WriteOnly);
QTextStream is(&input, QIODevice::ReadOnly);
Q_ASSERT(fmt);
QString modeline("// kate: ");
QString length = QString::number(indentation.length);
// add indentation style
modeline.append("indent-mode ").append(indentationMode(mime).append("; "));
ISourceFormatter::IndentationType type = indentation.type;
if (type == ISourceFormatter::IndentWithTabs) {
modeline.append("replace-tabs off; ");
} else if(type != ISourceFormatter::NoChange) {
modeline.append("space-indent on; ");
if (type == ISourceFormatter::IndentWithSpacesAndConvertTabs)
modeline.append("replace-tabs on; ");
}
if( indentation.length != 0 )
+ {
+ modeline.append("indent-width ").append(length).append("; ");
modeline.append("tab-width ").append(length).append("; ");
+ }
kDebug() << "created modeline: " << modeline << endl;
QRegExp kateModeline("^\\s*//\\s*kate:(.*)$");
bool modelinefound = false;
QRegExp knownOptions("\\s*(indent-width|space-indent|tab-width|indent-mode|replace-tabs)");
while (!is.atEnd()) {
QString line = is.readLine();
// replace only the options we care about
if (kateModeline.indexIn(line) >= 0) { // match
kDebug() << "Found a kate modeline: " << line << endl;
modelinefound = true;
QString options = kateModeline.cap(1);
QStringList optionList = options.split(';', QString::SkipEmptyParts);
os << modeline;
- foreach(const QString &s, optionList) {
+ foreach(QString s, optionList) {
if (knownOptions.indexIn(s) < 0) { // unknown option, add it
+ if(s.startsWith(" "))
+ s=s.mid(1);
os << s << ";";
kDebug() << "Found unknown option: " << s << endl;
}
}
os << endl;
} else
os << line << endl;
}
if (!modelinefound)
os << modeline << endl;
return output;
}
void SourceFormatterController::cleanup()
{
}
void SourceFormatterController::activeDocumentChanged(IDocument* doc)
{
bool enabled = false;
if (doc) {
KMimeType::Ptr mime = KMimeType::findByUrl(doc->url());
if (isMimeTypeSupported(mime))
enabled = true;
}
m_formatTextAction->setEnabled(enabled);
}
void SourceFormatterController::beautifySource()
{
KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController();
KDevelop::IDocument *doc = docController->activeDocument();
if (!doc)
return;
// load the appropriate formatter
KMimeType::Ptr mime = KMimeType::findByUrl(doc->url());
ISourceFormatter *formatter = formatterForMimeType(mime);
if( !formatter ) {
kDebug() << "no formatter available for" << mime;
return;
}
- adaptEditorIndentationMode( doc, formatter );
+ // Ignore the modeline, as the modeline will be changed anyway
+ adaptEditorIndentationMode( doc, formatter, true );
bool has_selection = false;
KTextEditor::View *view = doc->textDocument()->activeView();
if (view && view->selection())
has_selection = true;
if (has_selection) {
QString original = view->selectionText();
QString output = formatter->formatSource(view->selectionText(), doc->url(), mime,
view->document()->text(KTextEditor::Range(KTextEditor::Cursor(0,0),view->selectionRange().start())),
view->document()->text(KTextEditor::Range(view->selectionRange().end(), view->document()->documentRange().end())));
//remove the final newline character, unless it should be there
if (!original.endsWith('\n') && output.endsWith('\n'))
output.resize(output.length() - 1);
//there was a selection, so only change the part of the text related to it
doc->textDocument()->replaceText(view->selectionRange(), output);
} else {
formatDocument(doc, formatter, mime);
}
}
void SourceFormatterController::beautifyLine()
{
KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController();
KDevelop::IDocument *doc = docController->activeDocument();
if (!doc || !doc->isTextDocument())
return;
KTextEditor::Document *tDoc = doc->textDocument();
if (!tDoc->activeView())
return;
// load the appropriate formatter
KMimeType::Ptr mime = KMimeType::findByUrl(doc->url());
ISourceFormatter *formatter = formatterForMimeType(mime);
if( !formatter ) {
kDebug() << "no formatter available for" << mime;
return;
}
const KTextEditor::Cursor cursor = tDoc->activeView()->cursorPosition();
const QString line = tDoc->line(cursor.line());
const QString prev = tDoc->text(KTextEditor::Range(0, 0, cursor.line(), 0));
const QString post = "\n" + tDoc->text(KTextEditor::Range(KTextEditor::Cursor(cursor.line() + 1, 0), tDoc->documentEnd()));
const QString formatted = formatter->formatSource(line, doc->url(), mime, prev, post);
tDoc->replaceText(KTextEditor::Range(cursor.line(), 0, cursor.line(), line.length()), formatted);
// advance cursor one line
tDoc->activeView()->setCursorPosition(KTextEditor::Cursor(cursor.line() + 1, 0));
}
void SourceFormatterController::formatDocument(KDevelop::IDocument *doc, ISourceFormatter *formatter, const KMimeType::Ptr &mime)
{
KTextEditor::Document *textDoc = doc->textDocument();
KTextEditor::Cursor cursor = doc->cursorPosition();
QString text = formatter->formatSource(textDoc->text(), doc->url(), mime);
text = addModelineForCurrentLang(text, doc->url(), mime);
textDoc->setText(text);
doc->setCursorPosition(cursor);
}
void SourceFormatterController::settingsChanged()
{
if( configuration().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey, true ) )
foreach( KDevelop::IDocument* doc, ICore::self()->documentController()->openDocuments() )
adaptEditorIndentationMode( doc, formatterForUrl(doc->url()) );
}
/**
* Kate commands:
* Use spaces for indentation:
* "set-replace-tabs 1"
* Use tabs for indentation (eventually mixed):
* "set-replace-tabs 0"
* Indent width:
* "set-indent-width X"
* Tab width:
* "set-tab-width X"
* */
-void SourceFormatterController::adaptEditorIndentationMode(KDevelop::IDocument *doc, ISourceFormatter *formatter)
+void SourceFormatterController::adaptEditorIndentationMode(KDevelop::IDocument *doc, ISourceFormatter *formatter, bool ignoreModeline )
{
if( !formatter || !configuration().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey, true ) )
return;
KTextEditor::Document *textDoc = doc->textDocument();
kDebug() << "adapting mode for" << doc->url();
Q_ASSERT(textDoc);
+
+ QRegExp kateModelineWithNewline("\\s*\\n//\\s*kate:(.*)$");
+
+ // modelines should always take precedence
+ if( !ignoreModeline && kateModelineWithNewline.indexIn( doc->textDocument()->text() ) != -1 )
+ {
+ kDebug() << "ignoring because a kate modeline was found";
+ return;
+ }
+
ISourceFormatter::Indentation indentation = formatter->indentation(doc->url());
if(indentation.isValid())
{
struct CommandCaller {
CommandCaller(KTextEditor::Document* _doc) : doc(_doc), ci(qobject_cast(doc->editor())) {
}
void operator()(QString cmd) {
KTextEditor::Command* command = ci->queryCommand( cmd );
Q_ASSERT(command);
QString msg;
kDebug() << "calling" << cmd;
if( !command->exec( doc->activeView(), cmd, msg ) )
kWarning() << "setting indentation width failed: " << msg;
}
KTextEditor::Document* doc;
KTextEditor::CommandInterface* ci;
} call(textDoc);
if( indentation.length )
{
call( QString("set-indent-width %1").arg(indentation.length) );
call( QString("set-tab-width %1").arg(indentation.length) );
}
if( indentation.type != KDevelop::ISourceFormatter::NoChange )
{
call( QString("set-replace-tabs %1").arg(
(indentation.type == KDevelop::ISourceFormatter::IndentWithSpaces ||
indentation.type == KDevelop::ISourceFormatter::IndentWithSpacesAndConvertTabs) ? 1 : 0 ) );
}
}else{
kDebug() << "found no valid indentation";
}
}
void SourceFormatterController::formatFiles()
{
if (m_prjItems.isEmpty())
return;
//get a list of all files in this folder recursively
QList folders;
foreach(KDevelop::ProjectBaseItem *item, m_prjItems) {
if (!item)
continue;
if (item->folder())
folders.append(item->folder());
else if (item->file())
m_urls.append(item->file()->url());
else if (item->target()) {
foreach(KDevelop::ProjectFileItem *f, item->fileList())
m_urls.append(f->url());
}
}
while (!folders.isEmpty()) {
KDevelop::ProjectFolderItem *item = folders.takeFirst();
foreach(KDevelop::ProjectFolderItem *f, item->folderList())
folders.append(f);
foreach(KDevelop::ProjectTargetItem *f, item->targetList()) {
foreach(KDevelop::ProjectFileItem *child, f->fileList())
m_urls.append(child->url());
}
foreach(KDevelop::ProjectFileItem *f, item->fileList())
m_urls.append(f->url());
}
formatFiles(m_urls);
}
void SourceFormatterController::formatFiles(KUrl::List &list)
{
//! \todo IStatus
for (int fileCount = 0; fileCount < list.size(); fileCount++) {
// check mimetype
KMimeType::Ptr mime = KMimeType::findByUrl(list[fileCount]);
kDebug() << "Checking file " << list[fileCount].pathOrUrl() << " of mime type " << mime->name() << endl;
ISourceFormatter *formatter = formatterForMimeType(mime);
if (!formatter) // unsupported mime type
continue;
// if the file is opened in the editor, format the text in the editor without saving it
KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController();
KDevelop::IDocument *doc = docController->documentForUrl(list[fileCount]);
if (doc) {
kDebug() << "Processing file " << list[fileCount].pathOrUrl() << "opened in editor" << endl;
formatDocument(doc, formatter, mime);
return;
}
kDebug() << "Processing file " << list[fileCount].pathOrUrl() << endl;
QString tmpFile, output;
if (KIO::NetAccess::download(list[fileCount], tmpFile, 0)) {
QFile file(tmpFile);
// read file
if (file.open(QFile::ReadOnly)) {
QTextStream is(&file);
output = formatter->formatSource(is.readAll(), list[fileCount], mime);
file.close();
} else
KMessageBox::error(0, i18n("Unable to read %1", list[fileCount].prettyUrl()));
//write new content
if (file.open(QFile::WriteOnly | QIODevice::Truncate)) {
QTextStream os(&file);
os << addModelineForCurrentLang(output, list[fileCount], mime);
file.close();
} else
KMessageBox::error(0, i18n("Unable to write to %1", list[fileCount].prettyUrl()));
if (!KIO::NetAccess::upload(tmpFile, list[fileCount], 0))
KMessageBox::error(0, KIO::NetAccess::lastErrorString());
KIO::NetAccess::removeTempFile(tmpFile);
} else
KMessageBox::error(0, KIO::NetAccess::lastErrorString());
}
}
KDevelop::ContextMenuExtension SourceFormatterController::contextMenuExtension(KDevelop::Context* context)
{
KDevelop::ContextMenuExtension ext;
m_urls.clear();
m_prjItems.clear();
if (context->hasType(KDevelop::Context::EditorContext))
{
if(m_formatTextAction->isEnabled())
ext.addAction(KDevelop::ContextMenuExtension::EditGroup, m_formatTextAction);
} else if (context->hasType(KDevelop::Context::FileContext)) {
KDevelop::FileContext* filectx = dynamic_cast(context);
m_urls = filectx->urls();
ext.addAction(KDevelop::ContextMenuExtension::EditGroup, m_formatFilesAction);
} else if (context->hasType(KDevelop::Context::CodeContext)) {
} else if (context->hasType(KDevelop::Context::ProjectItemContext)) {
KDevelop::ProjectItemContext* prjctx = dynamic_cast(context);
m_prjItems = prjctx->items();
if ( !m_prjItems.isEmpty() ) {
ext.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, m_formatFilesAction);
}
}
return ext;
}
SourceFormatterStyle SourceFormatterController::styleForMimeType( const KMimeType::Ptr& mime )
{
QStringList formatter = configuration().readEntry( mime->name(), "" ).split( "||", QString::SkipEmptyParts );
if( formatter.count() == 2 )
{
SourceFormatterStyle s( formatter.at( 1 ) );
KConfigGroup fmtgrp = configuration().group( formatter.at(0) );
if( fmtgrp.hasGroup( formatter.at(1) ) ) {
KConfigGroup stylegrp = fmtgrp.group( formatter.at(1) );
s.setCaption( stylegrp.readEntry( styleCaptionKey, "" ) );
s.setContent( stylegrp.readEntry( styleContentKey, "" ) );
}
return s;
}
return SourceFormatterStyle();
}
/*
Code copied from source formatter plugin, unused currently but shouldn't be just thrown away
QString SourceFormatterPlugin::replaceSpacesWithTab(const QString &input, ISourceFormatter *formatter)
{
QString output(input);
int wsCount = formatter->indentationLength();
ISourceFormatter::IndentationType type = formatter->indentationType();
if (type == ISourceFormatter::IndentWithTabs) {
// tabs and wsCount spaces to be a tab
QString replace;
for (int i = 0; i < wsCount;i++)
replace += ' ';
output = output.replace(replace, QChar('\t'));
// input = input.remove(' ');
} else if (type == ISourceFormatter::IndentWithSpacesAndConvertTabs) {
//convert tabs to spaces
QString replace;
for (int i = 0;i < wsCount;i++)
replace += ' ';
output = output.replace(QChar('\t'), replace);
}
return output;
}
QString SourceFormatterPlugin::addIndentation(QString input, const QString indentWith)
{
QString output;
QTextStream os(&output, QIODevice::WriteOnly);
QTextStream is(&input, QIODevice::ReadOnly);
while (!is.atEnd())
os << indentWith << is.readLine() << endl;
return output;
}
*/
}
#include "sourceformattercontroller.moc"
// kate: indent-mode cstyle; space-indent off; tab-width 4;
diff --git a/shell/sourceformattercontroller.h b/shell/sourceformattercontroller.h
index cc88ea9670..5ba89b64ba 100644
--- a/shell/sourceformattercontroller.h
+++ b/shell/sourceformattercontroller.h
@@ -1,119 +1,119 @@
/* This file is part of KDevelop
Copyright 2009 Andreas Pakulat
Copyright (C) 2008 Cédric Pasteur
This library 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 library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef SOURCEFORMATTERCONTROLLER_H
#define SOURCEFORMATTERCONTROLLER_H
#include
#include
#include
#include
#include
#include
#include
#include
#include "shellexport.h"
class KAction;
namespace KDevelop
{
class Context;
class ContextMenuExtension;
class ProjectBaseItem;
class IDocument;
class ISourceFormatter;
class IPlugin;
/** \short A singleton class managing all source formatter plugins
*/
class KDEVPLATFORMSHELL_EXPORT SourceFormatterController : public ISourceFormatterController, public KXMLGUIClient
{
Q_OBJECT
public:
static const QString kateModeLineConfigKey;
static const QString kateOverrideIndentationConfigKey;
static const QString styleCaptionKey;
static const QString styleContentKey;
static const QString supportedMimeTypesKey;
SourceFormatterController(QObject *parent = 0);
virtual ~SourceFormatterController();
void initialize();
void cleanup();
//----------------- Public API defined in interfaces -------------------
/** \return The formatter corresponding to the language
* of the document corresponding to the \arg url.
*/
ISourceFormatter* formatterForUrl(const KUrl &url);
/** Loads and returns a source formatter for this mime type.
* The language is then activated and the style is loaded.
* The source formatter is then ready to use on a file.
*/
ISourceFormatter* formatterForMimeType(const KMimeType::Ptr &mime);
/** \return Whether this mime type is supported by any plugin.
*/
bool isMimeTypeSupported(const KMimeType::Ptr &mime);
KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context);
virtual KDevelop::SourceFormatterStyle styleForMimeType( const KMimeType::Ptr& mime );
KConfigGroup configuration();
void settingsChanged();
private Q_SLOTS:
void activeDocumentChanged(KDevelop::IDocument *doc);
void beautifySource();
void beautifyLine();
void formatFiles();
void documentLoaded( KDevelop::IDocument* );
private:
/** \return A modeline string (to add at the end or the beginning of a file)
* corresponding to the settings of the active language.
*/
QString addModelineForCurrentLang(QString input, const KUrl& url, const KMimeType::Ptr&);
/** \return The name of kate indentation mode for the mime type.
* examples are cstyle, python, etc.
*/
QString indentationMode(const KMimeType::Ptr &mime);
void formatDocument(KDevelop::IDocument *doc, ISourceFormatter *formatter, const KMimeType::Ptr &mime);
// Adapts the mode of the editor regarding indentation-style
- void adaptEditorIndentationMode(KDevelop::IDocument *doc, ISourceFormatter *formatter);
+ void adaptEditorIndentationMode(KDevelop::IDocument* doc, KDevelop::ISourceFormatter* formatter, bool ignoreModeline = false);
void formatFiles(KUrl::List &list);
// GUI actions
KAction* m_formatTextAction;
KAction* m_formatFilesAction;
KAction* m_formatLine;
QList m_prjItems;
KUrl::List m_urls;
};
}
#endif // SOURCEFORMATTERMANAGER_H
// kate: indent-mode cstyle; space-indent off; tab-width 4;