diff --git a/language/codegen/coderepresentation.cpp b/language/codegen/coderepresentation.cpp index 704c00075a..fee1f4e528 100644 --- a/language/codegen/coderepresentation.cpp +++ b/language/codegen/coderepresentation.cpp @@ -1,371 +1,413 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #include "coderepresentation.h" #include #include #include #include #include #include #include +#include +#include namespace KDevelop { static bool onDiskChangesForbidden = false; QString CodeRepresentation::rangeText(const KTextEditor::Range& range) const { Q_ASSERT(range.end().line() < lines()); //Easier for single line ranges which should happen most of the time if(range.onSingleLine()) return QString( line( range.start().line() ).mid( range.start().column(), range.columnWidth() ) ); //Add up al the requested lines QString rangedText = line(range.start().line()).mid(range.start().column()); for(int i = range.start().line() + 1; i <= range.end().line(); ++i) rangedText += '\n' + ((i == range.end().line()) ? line(i).left(range.end().column()) : line(i)); return rangedText; } static void grepLine(const QString& identifier, const QString& lineText, int lineNumber, QVector& ret, bool surroundedByBoundary) { if (identifier.isEmpty()) return; int pos = 0; while(true) { pos = lineText.indexOf(identifier, pos); if(pos == -1) break; int start = pos; pos += identifier.length(); int end = pos; if(!surroundedByBoundary || ( (end == lineText.length() || !lineText[end].isLetterOrNumber() || lineText[end] != '_') && (start-1 < 0 || !lineText[start-1].isLetterOrNumber() || lineText[start-1] != '_')) ) { ret << SimpleRange(lineNumber, start, lineNumber, end); } } - } +//NOTE: this is ugly, but otherwise kate might remove tabs again :-/ +// see also: https://bugs.kde.org/show_bug.cgi?id=291074 +struct EditorDisableReplaceTabs { + EditorDisableReplaceTabs(KTextEditor::Document* document) : m_iface(qobject_cast(document)), m_count(0) { + } + + void start() { + ++m_count; + if( m_count > 1 ) + return; + if ( m_iface ) { + m_oldReplaceTabs = m_iface->configValue( "replace-tabs" ); + m_iface->setConfigValue( "replace-tabs", false ); + } + } + + void end() { + --m_count; + if( m_count > 0 ) + return; + + Q_ASSERT( m_count == 0 ); + + if (m_iface) + m_iface->setConfigValue("replace-tabs", m_oldReplaceTabs); + } + + KTextEditor::ConfigInterface* m_iface; + int m_count; + QVariant m_oldReplaceTabs; +}; + class EditorCodeRepresentation : public DynamicCodeRepresentation { public: - EditorCodeRepresentation(KTextEditor::Document* document) : m_document(document) { + EditorCodeRepresentation(KTextEditor::Document* document) : m_document(document), m_replaceTabs(document) { m_url = IndexedString(m_document->url()); } virtual QVector< SimpleRange > grep ( const QString& identifier, bool surroundedByBoundary ) const { QVector< SimpleRange > ret; if (identifier.isEmpty()) return ret; for(int line = 0; line < m_document->lines(); ++line) grepLine(identifier, m_document->line(line), line, ret, surroundedByBoundary); return ret; } QString line(int line) const { if(line < 0 || line >= m_document->lines()) return QString(); return m_document->line(line); } virtual int lines() const { return m_document->lines(); } QString text() const { return m_document->text(); } bool setText(const QString& text) { + + startEdit(); bool ret = m_document->setText(text); + endEdit(); ModificationRevision::clearModificationCache(m_url); return ret; } bool fileExists(){ return QFile(m_document->url().path()).exists(); } void startEdit() { m_document->startEditing(); + m_replaceTabs.start(); } void endEdit() { m_document->endEditing(); + m_replaceTabs.end(); } bool replace(const KTextEditor::Range& range, const QString& oldText, const QString& newText, bool ignoreOldText) { QString old = m_document->text(range); if(oldText != old && !ignoreOldText) { return false; } - + + startEdit(); bool ret = m_document->replaceText(range, newText); + endEdit(); + ModificationRevision::clearModificationCache(m_url); - + return ret; } virtual QString rangeText(const KTextEditor::Range& range) const { return m_document->text(range); } private: KTextEditor::Document* m_document; IndexedString m_url; + EditorDisableReplaceTabs m_replaceTabs; }; class FileCodeRepresentation : public CodeRepresentation { public: FileCodeRepresentation(const IndexedString& document) : m_document(document) { QString localFile(document.toUrl().toLocalFile()); QFile file( localFile ); if ( file.open(QIODevice::ReadOnly) ) { data = QString::fromLocal8Bit(file.readAll()); lineData = data.split('\n'); } m_exists = file.exists(); } QString line(int line) const { if(line < 0 || line >= lineData.size()) return QString(); return lineData.at(line); } virtual QVector< SimpleRange > grep ( const QString& identifier, bool surroundedByBoundary ) const { QVector< SimpleRange > ret; if (identifier.isEmpty()) return ret; for(int line = 0; line < lineData.count(); ++line) grepLine(identifier, lineData.at(line), line, ret, surroundedByBoundary); return ret; } virtual int lines() const { return lineData.count(); } QString text() const { return data; } bool setText(const QString& text) { Q_ASSERT(!onDiskChangesForbidden); QString localFile(m_document.toUrl().toLocalFile()); QFile file( localFile ); if ( file.open(QIODevice::WriteOnly) ) { QByteArray data = text.toLocal8Bit(); if(file.write(data) == data.size()) { ModificationRevision::clearModificationCache(m_document); return true; } } return false; } bool fileExists(){ return m_exists; } private: //We use QByteArray, because the column-numbers are measured in utf-8 IndexedString m_document; bool m_exists; QStringList lineData; QString data; }; class ArtificialStringData : public QSharedData { public: ArtificialStringData(const QString& data) { setData(data); } void setData(const QString& data) { m_data = data; m_lineData = m_data.split('\n'); } QString data() const { return m_data; } const QStringList& lines() const { return m_lineData; } private: QString m_data; QStringList m_lineData; }; class StringCodeRepresentation : public CodeRepresentation { public: StringCodeRepresentation(KSharedPtr _data) : data(_data) { Q_ASSERT(data); } QString line(int line) const { if(line < 0 || line >= data->lines().size()) return QString(); return data->lines().at(line); } virtual int lines() const { return data->lines().count(); } QString text() const { return data->data(); } bool setText(const QString& text) { data->setData(text); return true; } bool fileExists(){ return false; } virtual QVector< SimpleRange > grep ( const QString& identifier, bool surroundedByBoundary ) const { QVector< SimpleRange > ret; if (identifier.isEmpty()) return ret; for(int line = 0; line < data->lines().count(); ++line) grepLine(identifier, data->lines().at(line), line, ret, surroundedByBoundary); return ret; } private: KSharedPtr data; }; static QHash > artificialStrings; //Return the representation for the given URL if it exists, or an empty pointer otherwise KSharedPtr representationForUrl(const IndexedString& url) { if(artificialStrings.contains(url)) return artificialStrings[url]; else { IndexedString constructedUrl(CodeRepresentation::artificialUrl(url.str())); if(artificialStrings.contains(constructedUrl)) return artificialStrings[constructedUrl]; else return KSharedPtr(); } } bool artificialCodeRepresentationExists(const IndexedString& url) { return !representationForUrl(url).isNull(); } CodeRepresentation::Ptr createCodeRepresentation(const IndexedString& url) { if(artificialCodeRepresentationExists(url)) return CodeRepresentation::Ptr(new StringCodeRepresentation(representationForUrl(url))); IDocument* document = ICore::self()->documentController()->documentForUrl(url.toUrl()); if(document && document->textDocument()) return CodeRepresentation::Ptr(new EditorCodeRepresentation(document->textDocument())); else return CodeRepresentation::Ptr(new FileCodeRepresentation(url)); } void CodeRepresentation::setDiskChangesForbidden(bool changesForbidden) { onDiskChangesForbidden = changesForbidden; } KUrl CodeRepresentation::artificialUrl(const QString& name) { KUrl url(name); url.setScheme("artificial"); url.cleanPath(); return url; } InsertArtificialCodeRepresentation::InsertArtificialCodeRepresentation(const IndexedString& file, const QString& text) : m_file(file) { if(m_file.toUrl().isRelative()) { m_file = IndexedString(CodeRepresentation::artificialUrl(file.str())); int idx = 0; while(artificialStrings.contains(m_file)) { ++idx; m_file = IndexedString(CodeRepresentation::artificialUrl(QString("%1_%2").arg(idx).arg(file.str()))); } } Q_ASSERT(!artificialStrings.contains(m_file)); artificialStrings.insert(m_file, KSharedPtr(new ArtificialStringData(text))); } IndexedString InsertArtificialCodeRepresentation::file() { return m_file; } InsertArtificialCodeRepresentation::~InsertArtificialCodeRepresentation() { Q_ASSERT(artificialStrings.contains(m_file)); artificialStrings.remove(m_file); } void InsertArtificialCodeRepresentation::setText(const QString& text) { Q_ASSERT(artificialStrings.contains(m_file)); artificialStrings[m_file]->setData(text); } QString InsertArtificialCodeRepresentation::text() { Q_ASSERT(artificialStrings.contains(m_file)); return artificialStrings[m_file]->data(); } } diff --git a/language/codegen/coderepresentation.h b/language/codegen/coderepresentation.h index 57e229d0b3..41ffcb19c0 100644 --- a/language/codegen/coderepresentation.h +++ b/language/codegen/coderepresentation.h @@ -1,138 +1,139 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 CODEREPRESENTATION_H #define CODEREPRESENTATION_H #include "../languageexport.h" #include #include class QString; namespace KTextEditor { class Range; } namespace KDevelop { class SimpleRange; class IndexedString; /** * Allows getting code-lines conveniently, either through an open editor, or from a disk-loaded file. */ class KDEVPLATFORMLANGUAGE_EXPORT CodeRepresentation : public QSharedData { public: virtual ~CodeRepresentation() { } virtual QString line(int line) const = 0; virtual int lines() const = 0; virtual QString text() const = 0; virtual QString rangeText(const KTextEditor::Range& range) const; /** * Search for the given identifier in the document, and returns all ranges * where it was found. * @param identifier The identifier to search for * @param surroundedByBoundary Whether only matches that are surrounded by typical word-boundaries * should be acceded. Everything except letters, numbers, and the _ character * counts as word boundary. * */ virtual QVector grep(const QString& identifier, bool surroundedByBoundary = true) const = 0; /** * Overwrites the text in the file with the new given one * * @return true on success */ virtual bool setText(const QString&) = 0; /** @return true if this representation represents an actual file on disk */ virtual bool fileExists() = 0; /** * Can be used for example from tests to disallow on-disk changes. When such a change is done, an assertion is triggered. * You should enable this within tests, unless you really want to work on the disk. */ static void setDiskChangesForbidden(bool changesForbidden); /** * Returns the specified name as a url for aritificial source code * suitable for code being inserted into the parser */ static KUrl artificialUrl(const QString & name); typedef KSharedPtr Ptr; }; class KDEVPLATFORMLANGUAGE_EXPORT DynamicCodeRepresentation : public CodeRepresentation { public: - /** Used to group edit-history together. Call this before a bunch of replace(), and endEdit in the end. */ + /** Used to group edit-history together. Call this optionally before a bunch + * of replace() calls, and then call endEdit in the end, to group them together. */ virtual void startEdit() = 0; virtual bool replace(const KTextEditor::Range& range, const QString& oldText, const QString& newText, bool ignoreOldText = false) = 0; /** Must be called exactly once per startEdit() */ virtual void endEdit() = 0; typedef KSharedPtr Ptr; }; /** * Creates a code-representation for the given url, that allows conveniently accessing its data. Returns zero on failure. */ KDEVPLATFORMLANGUAGE_EXPORT CodeRepresentation::Ptr createCodeRepresentation(const IndexedString& url); /** * @return true if an artificial code representation already exists for the specified URL */ KDEVPLATFORMLANGUAGE_EXPORT bool artificialCodeRepresentationExists(const IndexedString& url); /** * Allows inserting artificial source-code into the code-representation and parsing framework. * The source-code logically represents a file. * * The artificial code is automatically removed when the object is destroyed. */ class KDEVPLATFORMLANGUAGE_EXPORT InsertArtificialCodeRepresentation : public QSharedData { public: /** * Inserts an artifial source-code representation with filename @p file and the contents @p text * If @p file is not an absolute path or url, it will be made absolute using the CodeRepresentation::artifialUrl() * function, while ensuring that the name is unique. */ InsertArtificialCodeRepresentation(const IndexedString& file, const QString& text); ~InsertArtificialCodeRepresentation(); void setText(const QString& text); QString text(); /** * Returns the file-name for this code-representation. */ IndexedString file(); private: InsertArtificialCodeRepresentation(const InsertArtificialCodeRepresentation&); InsertArtificialCodeRepresentation& operator=(const InsertArtificialCodeRepresentation&); IndexedString m_file; }; typedef KSharedPtr InsertArtificialCodeRepresentationPointer; } #endif diff --git a/shell/sourceformattercontroller.cpp b/shell/sourceformattercontroller.cpp index 369f5330e8..24749ccd78 100644 --- a/shell/sourceformattercontroller.cpp +++ b/shell/sourceformattercontroller.cpp @@ -1,616 +1,619 @@ /* 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. */ #include "sourceformattercontroller.h" #include #include #include #include #include #include #include #include #include +#include #include #include "core.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "plugincontroller.h" #include namespace KDevelop { const QString SourceFormatterController::kateModeLineConfigKey = "ModelinesEnabled"; const QString SourceFormatterController::kateOverrideIndentationConfigKey = "OverrideKateIndentation"; 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 source

Source 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 Files

Formatting 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*))); // Use a queued connection, because otherwise the view is not yet fully set up connect(Core::self()->documentController(), SIGNAL(documentLoaded(KDevelop::IDocument*)), this, SLOT(documentLoaded(KDevelop::IDocument*)), Qt::QueuedConnection); activeDocumentChanged(Core::self()->documentController()->activeDocument()); } void SourceFormatterController::documentLoaded( IDocument* doc ) { // NOTE: explicitly check this here to prevent crashes on shutdown // when this slot gets called (note: delayed connection) // but the text document was already destroyed // there have been unit tests that failed due to that... if (!doc->textDocument()) { return; } 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 indentLength = QString::number(indentation.indentWidth); QString tabLength = QString::number(indentation.indentationTabWidth); // add indentation style modeline.append("indent-mode ").append(indentationMode(mime).append("; ")); if(indentation.indentWidth) // We know something about indentation-width modeline.append(QString("indent-width %1; ").arg(indentation.indentWidth)); if(indentation.indentationTabWidth != 0) // We know something about tab-usage { modeline.append(QString("replace-tabs %1; ").arg((indentation.indentationTabWidth == -1) ? "on" : "off")); if(indentation.indentationTabWidth > 0) modeline.append(QString("tab-width %1; ").arg(indentation.indentationTabWidth)); } 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(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; } // 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; - //NOTE: this is ugly, but otherwise kate might remove tabs again :-/ - // see also: https://bugs.kde.org/show_bug.cgi?id=291074 - KTextEditor::ConfigInterface* iface = qobject_cast(doc->textDocument()); - QVariant oldReplaceTabs; - if (iface) { - oldReplaceTabs = iface->configValue("replace-tabs"); - iface->setConfigValue("replace-tabs", false); - } - 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); + + // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works + // around a possible tab-replacement incompatibility between kate and kdevelop + DynamicCodeRepresentation::Ptr code = DynamicCodeRepresentation::Ptr::dynamicCast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ) ); + Q_ASSERT( code ); + code->replace( view->selectionRange(), original, output ); } else { formatDocument(doc, formatter, mime); } - if (iface) { - iface->setConfigValue("replace-tabs", oldReplaceTabs); - } } 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); + + // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works + // around a possible tab-replacement incompatibility between kate and kdevelop + DynamicCodeRepresentation::Ptr code = DynamicCodeRepresentation::Ptr::dynamicCast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ) ); + Q_ASSERT( code ); + code->replace( KTextEditor::Range(cursor.line(), 0, cursor.line(), line.length()), line, 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(); + // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works + // around a possible tab-replacement incompatibility between kate and kdevelop + CodeRepresentation::Ptr code = KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ); KTextEditor::Cursor cursor = doc->cursorPosition(); - QString text = formatter->formatSource(textDoc->text(), doc->url(), mime); + QString text = formatter->formatSource(code->text(), doc->url(), mime); text = addModelineForCurrentLang(text, doc->url(), mime); - textDoc->setText(text); + code->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, bool ignoreModeline ) { if( !formatter || !configuration().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey, true ) || !doc->isTextDocument() ) 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( textDoc->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())) { Q_ASSERT(ci); } 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.indentWidth ) // We know something about indentation-width call( QString("set-indent-width %1").arg(indentation.indentWidth ) ); if( indentation.indentationTabWidth != 0 ) // We know something about tab-usage { call( QString("set-replace-tabs %1").arg( (indentation.indentationTabWidth == -1) ? 1 : 0 ) ); if( indentation.indentationTabWidth > 0 ) call( QString("set-tab-width %1").arg(indentation.indentationTabWidth ) ); } }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); continue; } 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;