OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / texteditor / basetextdocument.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "basetextdocument.h"
35
36 #include "basetextdocumentlayout.h"
37 #include "basetexteditor.h"
38 #include "storagesettings.h"
39 #include "tabsettings.h"
40 #include "syntaxhighlighter.h"
41
42 #include <QtCore/QFile>
43 #include <QtCore/QDir>
44 #include <QtCore/QFileInfo>
45 #include <QtCore/QTextStream>
46 #include <QtCore/QTextCodec>
47 #include <QtGui/QMainWindow>
48 #include <QtGui/QSyntaxHighlighter>
49 #include <QtGui/QApplication>
50
51 #include <coreplugin/editormanager/editormanager.h>
52 #include <coreplugin/icore.h>
53 #include <utils/qtcassert.h>
54 #include <utils/reloadpromptutils.h>
55
56 namespace TextEditor {
57 namespace Internal {
58
59 class DocumentMarker : public ITextMarkable
60 {
61     Q_OBJECT
62 public:
63     DocumentMarker(QTextDocument *);
64
65     // ITextMarkable
66     bool addMark(ITextMark *mark, int line);
67     TextMarks marksAt(int line) const;
68     void removeMark(ITextMark *mark);
69     bool hasMark(ITextMark *mark) const;
70     void updateMark(ITextMark *mark);
71
72 private:
73     QTextDocument *document;
74 };
75
76 DocumentMarker::DocumentMarker(QTextDocument *doc)
77   : ITextMarkable(doc), document(doc)
78 {
79 }
80
81 bool DocumentMarker::addMark(TextEditor::ITextMark *mark, int line)
82 {
83     QTC_ASSERT(line >= 1, return false);
84     int blockNumber = line - 1;
85     BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(document->documentLayout());
86     QTC_ASSERT(documentLayout, return false);
87     QTextBlock block = document->findBlockByNumber(blockNumber);
88
89     if (block.isValid()) {
90         TextBlockUserData *userData = BaseTextDocumentLayout::userData(block);
91         userData->addMark(mark);
92         mark->updateLineNumber(blockNumber + 1);
93         mark->updateBlock(block);
94         documentLayout->hasMarks = true;
95         documentLayout->requestUpdate();
96         return true;
97     }
98     return false;
99 }
100
101 TextEditor::TextMarks DocumentMarker::marksAt(int line) const
102 {
103     QTC_ASSERT(line >= 1, return TextMarks());
104     int blockNumber = line - 1;
105     QTextBlock block = document->findBlockByNumber(blockNumber);
106
107     if (block.isValid()) {
108         if (TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(block))
109             return userData->marks();
110     }
111     return TextMarks();
112 }
113
114 void DocumentMarker::removeMark(TextEditor::ITextMark *mark)
115 {
116     bool needUpdate = false;
117     QTextBlock block = document->begin();
118     while (block.isValid()) {
119         if (TextBlockUserData *data = static_cast<TextBlockUserData *>(block.userData())) {
120             needUpdate |= data->removeMark(mark);
121         }
122         block = block.next();
123     }
124     if (needUpdate)
125         updateMark(0);
126 }
127
128 bool DocumentMarker::hasMark(TextEditor::ITextMark *mark) const
129 {
130     QTextBlock block = document->begin();
131     while (block.isValid()) {
132         if (TextBlockUserData *data = static_cast<TextBlockUserData *>(block.userData())) {
133             if (data->hasMark(mark))
134                 return true;
135         }
136         block = block.next();
137     }
138     return false;
139 }
140
141 void DocumentMarker::updateMark(ITextMark *mark)
142 {
143     Q_UNUSED(mark)
144     BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(document->documentLayout());
145     QTC_ASSERT(documentLayout, return);
146     documentLayout->requestUpdate();
147 }
148
149 } // namespace Internal
150
151 class BaseTextDocumentPrivate
152 {
153 public:
154     explicit BaseTextDocumentPrivate(BaseTextDocument *q);
155
156     QString m_fileName;
157     QString m_defaultPath;
158     QString m_suggestedFileName;
159     QString m_mimeType;
160     StorageSettings m_storageSettings;
161     TabSettings m_tabSettings;
162     QTextDocument *m_document;
163     Internal::DocumentMarker *m_documentMarker;
164     SyntaxHighlighter *m_highlighter;
165
166     enum LineTerminatorMode {
167         LFLineTerminator,
168         CRLFLineTerminator,
169         NativeLineTerminator =
170 #if defined (Q_OS_WIN)
171         CRLFLineTerminator
172 #else
173         LFLineTerminator
174 #endif
175     };
176     LineTerminatorMode m_lineTerminatorMode;
177     QTextCodec *m_codec;
178     bool m_fileHasUtf8Bom;
179
180     bool m_fileIsReadOnly;
181     bool m_isBinaryData;
182     bool m_hasDecodingError;
183     QByteArray m_decodingErrorSample;
184 };
185
186 BaseTextDocumentPrivate::BaseTextDocumentPrivate(BaseTextDocument *q) :
187     m_document(new QTextDocument(q)),
188     m_documentMarker(new Internal::DocumentMarker(m_document)),
189     m_highlighter(0),
190     m_lineTerminatorMode(NativeLineTerminator),
191     m_codec(Core::EditorManager::instance()->defaultTextEncoding()),
192     m_fileHasUtf8Bom(false),
193     m_fileIsReadOnly(false),
194     m_isBinaryData(false),
195     m_hasDecodingError(false)
196 {
197 }
198
199 BaseTextDocument::BaseTextDocument() : d(new BaseTextDocumentPrivate(this))
200 {
201 }
202
203 BaseTextDocument::~BaseTextDocument()
204 {
205     documentClosing();
206     delete d->m_document;
207     d->m_document = 0;
208     delete d;
209 }
210
211 QString BaseTextDocument::mimeType() const
212 {
213     return d->m_mimeType;
214 }
215
216 void BaseTextDocument::setMimeType(const QString &mt)
217 {
218     d->m_mimeType = mt;
219 }
220
221 void BaseTextDocument::setStorageSettings(const StorageSettings &storageSettings)
222 {
223     d->m_storageSettings = storageSettings;
224 }
225
226 const StorageSettings &BaseTextDocument::storageSettings() const
227 {
228     return d->m_storageSettings;
229 }
230
231 void BaseTextDocument::setTabSettings(const TabSettings &tabSettings)
232 {
233     d->m_tabSettings = tabSettings;
234 }
235
236 const TabSettings &BaseTextDocument::tabSettings() const
237 {
238     return d->m_tabSettings;
239 }
240
241 QString BaseTextDocument::fileName() const
242 {
243     return d->m_fileName;
244 }
245
246 bool BaseTextDocument::isSaveAsAllowed() const
247 {
248     return true;
249 }
250
251 QString BaseTextDocument::defaultPath() const
252 {
253     return d->m_defaultPath;
254 }
255
256 QString BaseTextDocument::suggestedFileName() const
257 {
258     return d->m_suggestedFileName;
259 }
260
261 void BaseTextDocument::setDefaultPath(const QString &defaultPath)
262 {
263     d->m_defaultPath = defaultPath;
264 }
265
266 void BaseTextDocument::setSuggestedFileName(const QString &suggestedFileName)
267 {
268     d->m_suggestedFileName = suggestedFileName;
269 }
270
271 QTextDocument *BaseTextDocument::document() const
272 {
273     return d->m_document;
274 }
275
276 SyntaxHighlighter *BaseTextDocument::syntaxHighlighter() const
277 {
278     return d->m_highlighter;
279 }
280
281 bool BaseTextDocument::isBinaryData() const
282 {
283     return d->m_isBinaryData;
284 }
285
286 bool BaseTextDocument::hasDecodingError() const
287 {
288     return d->m_hasDecodingError || d->m_isBinaryData;
289 }
290
291 QTextCodec *BaseTextDocument::codec() const
292 {
293     return d->m_codec;
294 }
295
296 void BaseTextDocument::setCodec(QTextCodec *c)
297 {
298     d->m_codec = c;
299 }
300
301 QByteArray BaseTextDocument::decodingErrorSample() const
302 {
303     return d->m_decodingErrorSample;
304 }
305
306 ITextMarkable *BaseTextDocument::documentMarker() const
307 {
308     return d->m_documentMarker;
309 }
310
311 bool BaseTextDocument::save(const QString &fileName)
312 {
313     QTextCursor cursor(d->m_document);
314
315     // When saving the current editor, make sure to maintain the cursor position for undo
316     Core::IEditor *currentEditor = Core::EditorManager::instance()->currentEditor();
317     if (BaseTextEditorEditable *editable = qobject_cast<BaseTextEditorEditable*>(currentEditor)) {
318         if (editable->file() == this)
319             cursor.setPosition(editable->editor()->textCursor().position());
320     }
321
322     cursor.beginEditBlock();
323     cursor.movePosition(QTextCursor::Start);
324
325     if (d->m_storageSettings.m_cleanWhitespace)
326         cleanWhitespace(cursor, d->m_storageSettings.m_cleanIndentation, d->m_storageSettings.m_inEntireDocument);
327     if (d->m_storageSettings.m_addFinalNewLine)
328         ensureFinalNewLine(cursor);
329     cursor.endEditBlock();
330
331     QString fName = d->m_fileName;
332     if (!fileName.isEmpty())
333         fName = fileName;
334
335     QFile file(fName);
336     if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
337         return false;
338
339     QString plainText = d->m_document->toPlainText();
340
341     if (d->m_lineTerminatorMode == BaseTextDocumentPrivate::CRLFLineTerminator)
342         plainText.replace(QLatin1Char('\n'), QLatin1String("\r\n"));
343
344     Core::IFile::Utf8BomSetting utf8bomSetting = Core::EditorManager::instance()->utf8BomSetting();
345     if (d->m_codec->name() == "UTF-8" &&
346         (utf8bomSetting == Core::IFile::AlwaysAdd || (utf8bomSetting == Core::IFile::OnlyKeep && d->m_fileHasUtf8Bom))) {
347         file.write("\xef\xbb\xbf", 3);
348     }
349
350     file.write(d->m_codec->fromUnicode(plainText));
351     if (!file.flush())
352         return false;
353     file.close();
354
355     const QFileInfo fi(fName);
356     d->m_fileName = QDir::cleanPath(fi.absoluteFilePath());
357
358     d->m_document->setModified(false);
359     emit titleChanged(fi.fileName());
360     emit changed();
361
362     d->m_isBinaryData = false;
363     d->m_hasDecodingError = false;
364     d->m_decodingErrorSample.clear();
365
366     return true;
367 }
368
369 void BaseTextDocument::rename(const QString &newName)
370 {
371     const QFileInfo fi(newName);
372     d->m_fileName = QDir::cleanPath(fi.absoluteFilePath());
373     emit titleChanged(fi.fileName());
374     emit changed();
375 }
376
377 bool BaseTextDocument::isReadOnly() const
378 {
379     if (d->m_isBinaryData || d->m_hasDecodingError)
380         return true;
381     if (d->m_fileName.isEmpty()) //have no corresponding file, so editing is ok
382         return false;
383     return d->m_fileIsReadOnly;
384 }
385
386 bool BaseTextDocument::isModified() const
387 {
388     return d->m_document->isModified();
389 }
390
391 void BaseTextDocument::checkPermissions()
392 {
393     bool previousReadOnly = d->m_fileIsReadOnly;
394     if (!d->m_fileName.isEmpty()) {
395         const QFileInfo fi(d->m_fileName);
396         d->m_fileIsReadOnly = !fi.isWritable();
397     } else {
398         d->m_fileIsReadOnly = false;
399     }
400     if (previousReadOnly != d->m_fileIsReadOnly)
401         emit changed();
402 }
403
404 bool BaseTextDocument::open(const QString &fileName)
405 {
406     QString title = tr("untitled");
407     if (!fileName.isEmpty()) {
408         const QFileInfo fi(fileName);
409         d->m_fileIsReadOnly = !fi.isWritable();
410         d->m_fileName = QDir::cleanPath(fi.absoluteFilePath());
411
412         QFile file(fileName);
413         if (!file.open(QIODevice::ReadOnly))
414             return false;
415
416         title = fi.fileName();
417
418         QByteArray buf = file.readAll();
419         int bytesRead = buf.size();
420
421         QTextCodec *codec = d->m_codec;
422         d->m_fileHasUtf8Bom = false;
423
424         // code taken from qtextstream
425         if (bytesRead >= 4 && ((uchar(buf[0]) == 0xff && uchar(buf[1]) == 0xfe && uchar(buf[2]) == 0 && uchar(buf[3]) == 0)
426                                || (uchar(buf[0]) == 0 && uchar(buf[1]) == 0 && uchar(buf[2]) == 0xfe && uchar(buf[3]) == 0xff))) {
427             codec = QTextCodec::codecForName("UTF-32");
428         } else if (bytesRead >= 2 && ((uchar(buf[0]) == 0xff && uchar(buf[1]) == 0xfe)
429                                       || (uchar(buf[0]) == 0xfe && uchar(buf[1]) == 0xff))) {
430             codec = QTextCodec::codecForName("UTF-16");
431         } else if (bytesRead >= 3 && ((uchar(buf[0]) == 0xef && uchar(buf[1]) == 0xbb) && uchar(buf[2]) == 0xbf)) {
432             codec = QTextCodec::codecForName("UTF-8");
433             d->m_fileHasUtf8Bom = true;
434         } else if (!codec) {
435             codec = QTextCodec::codecForLocale();
436         }
437         // end code taken from qtextstream
438
439         d->m_codec = codec;
440
441 #if 0 // should work, but does not, Qt bug with "system" codec
442         QTextDecoder *decoder = d->m_codec->makeDecoder();
443         QString text = decoder->toUnicode(buf);
444         d->m_hasDecodingError = (decoder->hasFailure());
445         delete decoder;
446 #else
447         QString text = d->m_codec->toUnicode(buf);
448         QByteArray verifyBuf = d->m_codec->fromUnicode(text); // slow
449         // the minSize trick lets us ignore unicode headers
450         int minSize = qMin(verifyBuf.size(), buf.size());
451         d->m_hasDecodingError = (minSize < buf.size()- 4
452                               || memcmp(verifyBuf.constData() + verifyBuf.size() - minSize,
453                                         buf.constData() + buf.size() - minSize, minSize));
454 #endif
455
456         if (d->m_hasDecodingError) {
457             int p = buf.indexOf('\n', 16384);
458             if (p < 0)
459                 d->m_decodingErrorSample = buf;
460             else
461                 d->m_decodingErrorSample = buf.left(p);
462         } else {
463             d->m_decodingErrorSample.clear();
464         }
465
466         int lf = text.indexOf('\n');
467         if (lf > 0 && text.at(lf-1) == QLatin1Char('\r')) {
468             d->m_lineTerminatorMode = BaseTextDocumentPrivate::CRLFLineTerminator;
469         } else if (lf >= 0) {
470             d->m_lineTerminatorMode = BaseTextDocumentPrivate::LFLineTerminator;
471         } else {
472             d->m_lineTerminatorMode = BaseTextDocumentPrivate::NativeLineTerminator;
473         }
474
475         d->m_document->setModified(false);
476         if (d->m_isBinaryData)
477             d->m_document->setHtml(tr("<em>Binary data</em>"));
478         else
479             d->m_document->setPlainText(text);
480         BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(d->m_document->documentLayout());
481         QTC_ASSERT(documentLayout, return true);
482         documentLayout->lastSaveRevision = d->m_document->revision();
483         d->m_document->setModified(false);
484         emit titleChanged(title);
485         emit changed();
486     }
487     return true;
488 }
489
490 void BaseTextDocument::reload(QTextCodec *codec)
491 {
492     QTC_ASSERT(codec, return);
493     d->m_codec = codec;
494     reload();
495 }
496
497 void BaseTextDocument::reload()
498 {
499     emit aboutToReload();
500     documentClosing(); // removes text marks non-permanently
501
502     if (open(d->m_fileName))
503         emit reloaded();
504 }
505
506 Core::IFile::ReloadBehavior BaseTextDocument::reloadBehavior(ChangeTrigger state, ChangeType type) const
507 {
508     if (type == TypePermissions)
509         return BehaviorSilent;
510     if (type == TypeContents) {
511         if (state == TriggerInternal && !isModified())
512             return BehaviorSilent;
513         return BehaviorAsk;
514     }
515     return BehaviorAsk;
516 }
517
518 void BaseTextDocument::reload(ReloadFlag flag, ChangeType type)
519 {
520     if (flag == FlagIgnore)
521         return;
522     if (type == TypePermissions) {
523         checkPermissions();
524     } else {
525         reload();
526     }
527 }
528
529 void BaseTextDocument::setSyntaxHighlighter(SyntaxHighlighter *highlighter)
530 {
531     if (d->m_highlighter)
532         delete d->m_highlighter;
533     d->m_highlighter = highlighter;
534     d->m_highlighter->setParent(this);
535     d->m_highlighter->setDocument(d->m_document);
536 }
537
538
539
540 void BaseTextDocument::cleanWhitespace(const QTextCursor &cursor)
541 {
542     bool hasSelection = cursor.hasSelection();
543     QTextCursor copyCursor = cursor;
544     copyCursor.setVisualNavigation(false);
545     copyCursor.beginEditBlock();
546     cleanWhitespace(copyCursor, true, true);
547     if (!hasSelection)
548         ensureFinalNewLine(copyCursor);
549     copyCursor.endEditBlock();
550 }
551
552 void BaseTextDocument::cleanWhitespace(QTextCursor &cursor, bool cleanIndentation, bool inEntireDocument)
553 {
554     BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(d->m_document->documentLayout());
555     Q_ASSERT(cursor.visualNavigation() == false);
556
557     QTextBlock block = d->m_document->findBlock(cursor.selectionStart());
558     QTextBlock end;
559     if (cursor.hasSelection())
560         end = d->m_document->findBlock(cursor.selectionEnd()-1).next();
561
562     while (block.isValid() && block != end) {
563
564         if (inEntireDocument || block.revision() != documentLayout->lastSaveRevision) {
565
566             QString blockText = block.text();
567             if (int trailing = d->m_tabSettings.trailingWhitespaces(blockText)) {
568                 cursor.setPosition(block.position() + block.length() - 1);
569                 cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, trailing);
570                 cursor.removeSelectedText();
571             }
572             if (cleanIndentation && !d->m_tabSettings.isIndentationClean(block)) {
573                 cursor.setPosition(block.position());
574                 int firstNonSpace = d->m_tabSettings.firstNonSpace(blockText);
575                 if (firstNonSpace == blockText.length()) {
576                     cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
577                     cursor.removeSelectedText();
578                 } else {
579                     int column = d->m_tabSettings.columnAt(blockText, firstNonSpace);
580                     cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, firstNonSpace);
581                     QString indentationString = d->m_tabSettings.indentationString(0, column, block);
582                     cursor.insertText(indentationString);
583                 }
584             }
585         }
586
587         block = block.next();
588     }
589 }
590
591 void BaseTextDocument::ensureFinalNewLine(QTextCursor& cursor)
592 {
593     cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
594     bool emptyFile = !cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
595
596     if (!emptyFile && cursor.selectedText().at(0) != QChar::ParagraphSeparator)
597     {
598         cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
599         cursor.insertText(QLatin1String("\n"));
600     }
601 }
602
603 void BaseTextDocument::documentClosing()
604 {
605     QTextBlock block = d->m_document->begin();
606     while (block.isValid()) {
607         if (TextBlockUserData *data = static_cast<TextBlockUserData *>(block.userData()))
608             data->documentClosing();
609         block = block.next();
610     }
611 }
612
613 } // namespace TextEditor
614
615 #include "basetextdocument.moc"