From abc23714db987e699476f6b6a3af0fe44e0bc0a2 Mon Sep 17 00:00:00 2001 From: Anthony Lee Stark Date: Fri, 23 May 2025 13:20:09 +0700 Subject: [PATCH] Add new feature of using first line of untitled document for its tab name Fix #3994, fix #16584, close #16585 --- PowerEditor/installer/nativeLang/english.xml | 1 + .../nativeLang/english_customizable.xml | 1 + .../installer/nativeLang/vietnamese.xml | 1 + PowerEditor/src/Notepad_plus.cpp | 2 +- PowerEditor/src/NppIO.cpp | 7 +++ PowerEditor/src/NppNotification.cpp | 53 +++++++++++++++++++ PowerEditor/src/Parameters.cpp | 17 +++++- PowerEditor/src/Parameters.h | 6 ++- PowerEditor/src/ScintillaComponent/Buffer.cpp | 42 +++++++++++++++ PowerEditor/src/ScintillaComponent/Buffer.h | 7 +++ .../src/WinControls/Preference/preference.rc | 3 +- .../WinControls/Preference/preferenceDlg.cpp | 8 +++ .../WinControls/Preference/preference_rc.h | 1 + 13 files changed, 143 insertions(+), 6 deletions(-) diff --git a/PowerEditor/installer/nativeLang/english.xml b/PowerEditor/installer/nativeLang/english.xml index f4ee8a273..205096b2d 100644 --- a/PowerEditor/installer/nativeLang/english.xml +++ b/PowerEditor/installer/nativeLang/english.xml @@ -1108,6 +1108,7 @@ Translation note: + diff --git a/PowerEditor/installer/nativeLang/english_customizable.xml b/PowerEditor/installer/nativeLang/english_customizable.xml index c7c4f4b57..1d9bff9df 100644 --- a/PowerEditor/installer/nativeLang/english_customizable.xml +++ b/PowerEditor/installer/nativeLang/english_customizable.xml @@ -1108,6 +1108,7 @@ Translation note: + diff --git a/PowerEditor/installer/nativeLang/vietnamese.xml b/PowerEditor/installer/nativeLang/vietnamese.xml index d9966da88..8bd5e7db3 100644 --- a/PowerEditor/installer/nativeLang/vietnamese.xml +++ b/PowerEditor/installer/nativeLang/vietnamese.xml @@ -1085,6 +1085,7 @@ Translation note: + diff --git a/PowerEditor/src/Notepad_plus.cpp b/PowerEditor/src/Notepad_plus.cpp index 5fb6bbde8..d1439dbb0 100644 --- a/PowerEditor/src/Notepad_plus.cpp +++ b/PowerEditor/src/Notepad_plus.cpp @@ -6360,7 +6360,7 @@ void Notepad_plus::getCurrentOpenedFiles(Session & session, bool includeUntitled } const wchar_t* langName = languageName.c_str(); - sessionFileInfo sfi(buf->getFullPathName(), langName, buf->getEncoding(), buf->getUserReadOnly(), buf->isPinned(), buf->getPosition(editView), buf->getBackupFileName().c_str(), buf->getLastModifiedTimestamp(), buf->getMapPosition()); + sessionFileInfo sfi(buf->getFullPathName(), langName, buf->getEncoding(), buf->getUserReadOnly(), buf->isPinned(), buf->isUntitledTabRenamed(), buf->getPosition(editView), buf->getBackupFileName().c_str(), buf->getLastModifiedTimestamp(), buf->getMapPosition()); sfi._isMonitoring = buf->isMonitoringOn(); sfi._individualTabColour = docTab[k]->getIndividualTabColourId(static_cast(i)); diff --git a/PowerEditor/src/NppIO.cpp b/PowerEditor/src/NppIO.cpp index bcc7fce0e..2e1cc3abf 100644 --- a/PowerEditor/src/NppIO.cpp +++ b/PowerEditor/src/NppIO.cpp @@ -2118,6 +2118,7 @@ bool Notepad_plus::fileRename(BufferID id) _pluginsManager.notify(&scnN); success = true; + buf->setUntitledTabRenamedStatus(true); bool isSnapshotMode = NppParameters::getInstance().getNppGUI().isSnapshotMode(); if (isSnapshotMode) @@ -2194,6 +2195,8 @@ bool Notepad_plus::fileRenameUntitledPluginAPI(BufferID id, const wchar_t* tabNe scnN.nmhdr.code = NPPN_FILERENAMED; _pluginsManager.notify(&scnN); + buf->setUntitledTabRenamedStatus(true); + bool isSnapshotMode = NppParameters::getInstance().getNppGUI().isSnapshotMode(); if (isSnapshotMode) { @@ -2489,6 +2492,8 @@ bool Notepad_plus::loadSession(Session & session, bool isSnapshotMode, const wch buf->setUserReadOnly(session._mainViewFiles[i]._isUserReadOnly); buf->setPinned(session._mainViewFiles[i]._isPinned); + buf->setUntitledTabRenamedStatus(session._mainViewFiles[i]._isUntitledTabRenamed); + if (isSnapshotMode && !session._mainViewFiles[i]._backupFilePath.empty() && doesFileExist(session._mainViewFiles[i]._backupFilePath.c_str())) buf->setDirty(true); @@ -2624,6 +2629,8 @@ bool Notepad_plus::loadSession(Session & session, bool isSnapshotMode, const wch buf->setUserReadOnly(session._subViewFiles[k]._isUserReadOnly); buf->setPinned(session._subViewFiles[k]._isPinned); + buf->setUntitledTabRenamedStatus(session._subViewFiles[k]._isUntitledTabRenamed); + if (isSnapshotMode && !session._subViewFiles[k]._backupFilePath.empty() && doesFileExist(session._subViewFiles[k]._backupFilePath.c_str())) buf->setDirty(true); diff --git a/PowerEditor/src/NppNotification.cpp b/PowerEditor/src/NppNotification.cpp index 361428be0..d14d6ae95 100644 --- a/PowerEditor/src/NppNotification.cpp +++ b/PowerEditor/src/NppNotification.cpp @@ -59,6 +59,59 @@ BOOL Notepad_plus::notify(SCNotification *notification) { // for the backup system _pEditView->getCurrentBuffer()->setModifiedStatus(true); + + // auto make temporary name for untitled documents + Buffer* buffer = notifyView->getCurrentBuffer(); + const NppGUI& nppGui = NppParameters::getInstance().getNppGUI(); + const NewDocDefaultSettings& ndds = nppGui.getNewDocDefaultSettings(); + intptr_t curLineIndex = _pEditView->execute(SCI_LINEFROMPOSITION, notification->position); + if (curLineIndex == 0 && ndds._useContentAsTabName && buffer->isUntitled() && !buffer->isUntitledTabRenamed()) + { + // make a temporary tab name from first line of document + wstring content1stLineTabName = _pEditView->getLine(0); + buffer->normalizeTabName(content1stLineTabName); + + // check whether there is any buffer with the same name + BufferID sameNamedBufferId = _pDocTab->findBufferByName(content1stLineTabName.c_str()); + if (sameNamedBufferId == BUFFER_INVALID) + sameNamedBufferId = _pNonDocTab->findBufferByName(content1stLineTabName.c_str()); + + if (!content1stLineTabName.empty() && content1stLineTabName != buffer->getFileName() && sameNamedBufferId == BUFFER_INVALID) + { + // notify tab name changing + SCNotification scnNotif{}; + scnNotif.nmhdr.code = NPPN_FILEBEFORERENAME; + scnNotif.nmhdr.hwndFrom = _pPublicInterface->getHSelf(); + scnNotif.nmhdr.idFrom = (uptr_t)buffer->getID(); + _pluginsManager.notify(&scnNotif); + + // backup old file path + wstring oldFileNamePath = buffer->getFullPathName(); + + // set tab name + buffer->setFileName(content1stLineTabName.c_str()); + + // notify tab renamed + scnNotif.nmhdr.code = NPPN_FILERENAMED; + _pluginsManager.notify(&scnNotif); + + // for the backup system + wstring oldBackUpFileName = buffer->getBackupFileName(); + bool isSnapshotMode = nppGui.isSnapshotMode(); + if (isSnapshotMode && !oldBackUpFileName.empty()) + { + wstring newBackUpFileName = oldBackUpFileName; + newBackUpFileName.replace(newBackUpFileName.rfind(oldFileNamePath), oldFileNamePath.length(), content1stLineTabName); + + if (doesFileExist(newBackUpFileName.c_str())) + ::ReplaceFile(newBackUpFileName.c_str(), oldBackUpFileName.c_str(), nullptr, REPLACEFILE_IGNORE_MERGE_ERRORS | REPLACEFILE_IGNORE_ACL_ERRORS, 0, 0); + else + ::MoveFileEx(oldBackUpFileName.c_str(), newBackUpFileName.c_str(), MOVEFILE_REPLACE_EXISTING); + + buffer->setBackupFileName(newBackUpFileName); + } + } + } } if (notification->modificationType & SC_MOD_CHANGEINDICATOR) diff --git a/PowerEditor/src/Parameters.cpp b/PowerEditor/src/Parameters.cpp index c8128b13b..5fb91e544 100644 --- a/PowerEditor/src/Parameters.cpp +++ b/PowerEditor/src/Parameters.cpp @@ -2449,7 +2449,12 @@ bool NppParameters::getSessionFromXmlTree(TiXmlDocument *pSessionDoc, Session& s if (boolStrPinned) isPinned = _wcsicmp(L"yes", boolStrPinned) == 0; - sessionFileInfo sfi(fileName, langName, encStr ? encoding : -1, isUserReadOnly, isPinned, position, pBackupFilePath, fileModifiedTimestamp, mapPosition); + bool isUntitleTabRenamed = false; + const wchar_t* boolStrTabRenamed = (childNode->ToElement())->Attribute(L"untitleTabRenamed"); + if (boolStrTabRenamed) + isUntitleTabRenamed = _wcsicmp(L"yes", boolStrTabRenamed) == 0; + + sessionFileInfo sfi(fileName, langName, encStr ? encoding : -1, isUserReadOnly, isPinned, isUntitleTabRenamed, position, pBackupFilePath, fileModifiedTimestamp, mapPosition); const wchar_t* intStrTabColour = (childNode->ToElement())->Attribute(L"tabColourId"); if (intStrTabColour) @@ -3674,6 +3679,9 @@ void NppParameters::writeSession(const Session & session, const wchar_t *fileNam (fileNameNode->ToElement())->SetAttribute(L"tabColourId", static_cast(viewSessionFiles[i]._individualTabColour)); (fileNameNode->ToElement())->SetAttribute(L"RTL", viewSessionFiles[i]._isRTL ? L"yes" : L"no"); (fileNameNode->ToElement())->SetAttribute(L"tabPinned", viewSessionFiles[i]._isPinned ? L"yes" : L"no"); + // Save this info only when it's an untitled entry + if (viewSessionFiles[i]._isUntitledTabRenamed) + (fileNameNode->ToElement())->SetAttribute(L"untitleTabRenamed", L"yes"); // docMap (fileNameNode->ToElement())->SetAttribute(L"mapFirstVisibleDisplayLine", _i64tot(static_cast(viewSessionFiles[i]._mapPos._firstVisibleDisplayLine), szInt64, 10)); @@ -5547,6 +5555,10 @@ void NppParameters::feedGUIParameters(TiXmlNode *node) val = element->Attribute(L"addNewDocumentOnStartup"); if (val) _nppGUI._newDocDefaultSettings._addNewDocumentOnStartup = (lstrcmp(val, L"yes") == 0); + + val = element->Attribute(L"useContentAsTabName"); + if (val) + _nppGUI._newDocDefaultSettings._useContentAsTabName = (lstrcmp(val, L"yes") == 0); } else if (!lstrcmp(nm, L"langsExcluded")) @@ -7517,7 +7529,7 @@ void NppParameters::createXmlTreeFromGUIParams() insertGUIConfigBoolNode(newGUIRoot, L"SaveAllConfirm", _nppGUI._saveAllConfirm); } - // + // { TiXmlElement *GUIConfigElement = (newGUIRoot->InsertEndChild(TiXmlElement(L"GUIConfig")))->ToElement(); GUIConfigElement->SetAttribute(L"name", L"NewDocDefaultSettings"); @@ -7527,6 +7539,7 @@ void NppParameters::createXmlTreeFromGUIParams() GUIConfigElement->SetAttribute(L"codepage", _nppGUI._newDocDefaultSettings._codepage); GUIConfigElement->SetAttribute(L"openAnsiAsUTF8", _nppGUI._newDocDefaultSettings._openAnsiAsUtf8 ? L"yes" : L"no"); GUIConfigElement->SetAttribute(L"addNewDocumentOnStartup", _nppGUI._newDocDefaultSettings._addNewDocumentOnStartup ? L"yes" : L"no"); + GUIConfigElement->SetAttribute(L"useContentAsTabName", _nppGUI._newDocDefaultSettings._useContentAsTabName ? L"yes" : L"no"); } // diff --git a/PowerEditor/src/Parameters.h b/PowerEditor/src/Parameters.h index 6e6a40568..73df53f8c 100644 --- a/PowerEditor/src/Parameters.h +++ b/PowerEditor/src/Parameters.h @@ -219,8 +219,8 @@ public: struct sessionFileInfo : public Position { - sessionFileInfo(const wchar_t* fn, const wchar_t *ln, int encoding, bool userReadOnly,bool isPinned, const Position& pos, const wchar_t *backupFilePath, FILETIME originalFileLastModifTimestamp, const MapPosition & mapPos) : - Position(pos), _encoding(encoding), _isUserReadOnly(userReadOnly), _isPinned(isPinned), _originalFileLastModifTimestamp(originalFileLastModifTimestamp), _mapPos(mapPos) + sessionFileInfo(const wchar_t* fn, const wchar_t *ln, int encoding, bool userReadOnly,bool isPinned, bool isUntitleTabRenamed, const Position& pos, const wchar_t *backupFilePath, FILETIME originalFileLastModifTimestamp, const MapPosition & mapPos) : + Position(pos), _encoding(encoding), _isUserReadOnly(userReadOnly), _isPinned(isPinned), _isUntitledTabRenamed(isUntitleTabRenamed), _originalFileLastModifTimestamp(originalFileLastModifTimestamp), _mapPos(mapPos) { if (fn) _fileName = fn; if (ln) _langName = ln; @@ -239,6 +239,7 @@ struct sessionFileInfo : public Position int _individualTabColour = -1; bool _isRTL = false; bool _isPinned = false; + bool _isUntitledTabRenamed = false; std::wstring _backupFilePath; FILETIME _originalFileLastModifTimestamp {}; @@ -609,6 +610,7 @@ struct NewDocDefaultSettings final LangType _lang = L_TEXT; int _codepage = -1; // -1 when not using bool _addNewDocumentOnStartup = false; + bool _useContentAsTabName = false; }; diff --git a/PowerEditor/src/ScintillaComponent/Buffer.cpp b/PowerEditor/src/ScintillaComponent/Buffer.cpp index 897c16e18..4d6a65dec 100644 --- a/PowerEditor/src/ScintillaComponent/Buffer.cpp +++ b/PowerEditor/src/ScintillaComponent/Buffer.cpp @@ -37,6 +37,10 @@ static const int LF = 0x0A; long Buffer::_recentTagCtr = 0; +// Invalid characters for a file name +// Refer: https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file +// Including 'tab' and 'return/new line' characters +const wchar_t* fileNameInvalidChars = L"\\/:*?\"<>|\t\r\n"; namespace // anonymous { @@ -304,6 +308,44 @@ void Buffer::setFileName(const wchar_t *fn) doNotify(BufferChangeFilename | BufferChangeTimestamp | lang2Change); } +void Buffer::normalizeTabName(wstring& tabName) +{ + if (!tabName.empty()) + { + // remove leading/trailing spaces + trim(tabName); + + // remove invalid characters + wstring tempStr; + for (wchar_t ch : tabName) + { + bool isInvalid = false; + for (const wchar_t* p = fileNameInvalidChars; *p != L'\0'; ++p) + { + if (ch == *p) + { + isInvalid = true; + break; + } + } + + if (!isInvalid) + tempStr += ch; + } + tabName = tempStr; + + // restrict length + if (tabName.length() >= langNameLenMax - 1) + { + tempStr = tabName.substr(0, langNameLenMax - 1); + tabName = tempStr; + } + + // remove leading/trailing spaces again + trim(tabName); + } +} + bool Buffer::checkFileState() // returns true if the status has been changed (it can change into DOC_REGULAR too). false otherwise { diff --git a/PowerEditor/src/ScintillaComponent/Buffer.h b/PowerEditor/src/ScintillaComponent/Buffer.h index f7f72b8cd..bf08431b7 100644 --- a/PowerEditor/src/ScintillaComponent/Buffer.h +++ b/PowerEditor/src/ScintillaComponent/Buffer.h @@ -170,6 +170,8 @@ public: const wchar_t * getFileName() const { return _fileName; } + void normalizeTabName(std::wstring& tabName); + BufferID getID() const { return _id; } void increaseRecentTag() { @@ -269,6 +271,9 @@ public: doNotify(BufferChangeLexing); } + bool isUntitledTabRenamed() const { return _isUntitledTabRenamed; } + void setUntitledTabRenamedStatus(bool isRenamed) { _isUntitledTabRenamed = isRenamed; } + //these two return reference count after operation int addReference(ScintillaEditView * identifier); //if ID not registered, creates a new Position for that ID and new foldstate int removeReference(const ScintillaEditView * identifier); //reduces reference. If zero, Document is purged @@ -399,6 +404,8 @@ private: bool _needLexer = false; // new buffers do not need lexing, Scintilla takes care of that //these properties have to be duplicated because of multiple references + bool _isUntitledTabRenamed = false; + //All the vectors must have the same size at all times std::vector _referees; // Instances of ScintillaEditView which contain this buffer std::vector _positions; diff --git a/PowerEditor/src/WinControls/Preference/preference.rc b/PowerEditor/src/WinControls/Preference/preference.rc index d0a4b52f6..99ec7d0e6 100644 --- a/PowerEditor/src/WinControls/Preference/preference.rc +++ b/PowerEditor/src/WinControls/Preference/preference.rc @@ -248,7 +248,7 @@ IDD_PREFERENCE_SUB_NEWDOCUMENT DIALOGEX 115, 10, 460, 205 STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD FONT 8, "MS Shell Dlg", 0, 0, 0x1 BEGIN - GROUPBOX "New Document",IDC_NEWDOCUMENT_GR_STATIC,15,8,424,161,BS_CENTER + GROUPBOX "New Document",IDC_NEWDOCUMENT_GR_STATIC,15,8,424,174,BS_CENTER GROUPBOX "Format (Line ending)",IDC_FORMAT_GB_STATIC,58,29,129,79,BS_CENTER CONTROL "Windows (CR LF)",IDC_RADIO_F_WIN,"Button",BS_AUTORADIOBUTTON | WS_GROUP,65,48,70,10 CONTROL "Unix (LF)",IDC_RADIO_F_UNIX,"Button",BS_AUTORADIOBUTTON,65,64,70,10 @@ -265,6 +265,7 @@ BEGIN RTEXT "Default language:",IDC_DEFAULTLANG_STATIC,16,125,77,8 COMBOBOX IDC_COMBO_DEFAULTLANG,98,123,100,140,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP CONTROL "Always open a new document in addition at startup",IDC_CHECK_ADDNEWDOCONSTARTUP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,152,250,10 + CONTROL "Use the first line of document as untitled tab name",IDC_CHECK_USECONTENTASTABNAME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,165,250,10 END diff --git a/PowerEditor/src/WinControls/Preference/preferenceDlg.cpp b/PowerEditor/src/WinControls/Preference/preferenceDlg.cpp index 5b393fbc6..a6928b1de 100644 --- a/PowerEditor/src/WinControls/Preference/preferenceDlg.cpp +++ b/PowerEditor/src/WinControls/Preference/preferenceDlg.cpp @@ -3419,6 +3419,8 @@ intptr_t CALLBACK NewDocumentSubDlg::run_dlgProc(UINT message, WPARAM wParam, LP ::SendDlgItemMessage(_hSelf, IDC_CHECK_ADDNEWDOCONSTARTUP, BM_SETCHECK, ndds._addNewDocumentOnStartup, 0); + ::SendDlgItemMessage(_hSelf, IDC_CHECK_USECONTENTASTABNAME, BM_SETCHECK, ndds._useContentAsTabName, 0); + return TRUE; } @@ -3516,6 +3518,12 @@ intptr_t CALLBACK NewDocumentSubDlg::run_dlgProc(UINT message, WPARAM wParam, LP return TRUE; } + case IDC_CHECK_USECONTENTASTABNAME: + { + ndds._useContentAsTabName = isCheckedOrNot(IDC_CHECK_USECONTENTASTABNAME); + return TRUE; + } + default: { if (HIWORD(wParam) == CBN_SELCHANGE) diff --git a/PowerEditor/src/WinControls/Preference/preference_rc.h b/PowerEditor/src/WinControls/Preference/preference_rc.h index 771bfbaea..3ea67cb27 100644 --- a/PowerEditor/src/WinControls/Preference/preference_rc.h +++ b/PowerEditor/src/WinControls/Preference/preference_rc.h @@ -318,6 +318,7 @@ #define IDC_DISPLAY_STATIC 6429 #define IDC_OPENSAVEDIR_CHECK_DROPFOLDEROPENFILES 6431 #define IDC_CHECK_ADDNEWDOCONSTARTUP 6432 +#define IDC_CHECK_USECONTENTASTABNAME 6433 #define IDD_PREFERENCE_SUB_DEFAULTDIRECTORY 6450 #define IDD_PREFERENCE_SUB_RECENTFILESHISTORY 6460