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