From c15cbbf607d7b13bcbf766835de8d19db35ed7c5 Mon Sep 17 00:00:00 2001 From: xiaoyifang <105986+xiaoyifang@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:04:30 +0800 Subject: [PATCH 01/67] opt: support UP/Down arrow key in headword UI (#1358) * opt:refactor the code * opt: headwords dialog response to the Up/Down Key * opt: when the headword come from headword dialog ,not focus * [autofix.ci] apply automated fixes --------- Co-authored-by: YiFang Xiao Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/instances.cc | 32 ++++++++++++++++---------------- src/instances.hh | 4 ++-- src/ui/dictheadwords.cc | 30 +++++++++++++++++++++++------- src/ui/mainwindow.cc | 11 ++++++++--- src/ui/mainwindow.hh | 5 ++++- 5 files changed, 53 insertions(+), 29 deletions(-) diff --git a/src/instances.cc b/src/instances.cc index 05d14690f..649812443 100644 --- a/src/instances.cc +++ b/src/instances.cc @@ -4,6 +4,7 @@ #include "instances.hh" #include #include +#include namespace Instances { @@ -27,7 +28,7 @@ Group::Group( Config::Group const & cfgGroup, auto dictMap = Dictionary::dictToMap( allDictionaries ); for ( auto const & dict : cfgGroup.dictionaries ) { - std::string dictId = dict.id.toStdString(); + std::string const dictId = dict.id.toStdString(); if ( dictMap.contains( dictId ) ) { groupDicts.insert( dictId, dictMap[ dictId ] ); @@ -37,9 +38,8 @@ Group::Group( Config::Group const & cfgGroup, // Remove inactive dictionaries if ( !inactiveGroup.dictionaries.isEmpty() ) { - set< string, std::less<> > inactiveSet; for ( auto const & dict : inactiveGroup.dictionaries ) { - string dictId = dict.id.toStdString(); + string const dictId = dict.id.toStdString(); groupDicts.remove( dictId ); dictOrderList.removeOne( dictId ); } @@ -51,15 +51,15 @@ Group::Group( Config::Group const & cfgGroup, } } -Group::Group( QString const & name_ ): +Group::Group( QString name_ ): id( 0 ), - name( name_ ) + name( std::move( name_ ) ) { } -Group::Group( unsigned id_, QString const & name_ ): +Group::Group( unsigned id_, QString name_ ): id( id_ ), - name( name_ ) + name( std::move( name_ ) ) { } @@ -102,9 +102,9 @@ void Group::checkMutedDictionaries( Config::MutedDictionaries * mutedDictionarie Config::MutedDictionaries temp; for ( auto const & dict : dictionaries ) { - QString id = QString::fromStdString( dict->getId() ); - if ( mutedDictionaries->contains( id ) ) - temp.insert( id ); + auto dictId = QString::fromStdString( dict->getId() ); + if ( mutedDictionaries->contains( dictId ) ) + temp.insert( dictId ); } *mutedDictionaries = temp; } @@ -139,9 +139,9 @@ void complementDictionaryOrder( Group & group, for ( unsigned x = inactiveDictionaries.dictionaries.size(); x--; ) presentIds.insert( inactiveDictionaries.dictionaries[ x ]->getId() ); - for ( unsigned x = 0; x < dicts.size(); ++x ) { - if ( presentIds.find( dicts[ x ]->getId() ) == presentIds.end() ) - group.dictionaries.push_back( dicts[ x ] ); + for ( const auto & dict : dicts ) { + if ( presentIds.find( dict->getId() ) == presentIds.end() ) + group.dictionaries.push_back( dict ); } } @@ -149,7 +149,7 @@ void updateNames( Config::Group & group, vector< sptr< Dictionary::Class > > con { for ( unsigned x = group.dictionaries.size(); x--; ) { - std::string id = group.dictionaries[ x ].id.toStdString(); + std::string const id = group.dictionaries[ x ].id.toStdString(); for ( unsigned y = allDictionaries.size(); y--; ) if ( allDictionaries[ y ]->getId() == id ) { @@ -161,8 +161,8 @@ void updateNames( Config::Group & group, vector< sptr< Dictionary::Class > > con void updateNames( Config::Groups & groups, vector< sptr< Dictionary::Class > > const & allDictionaries ) { - for ( int x = 0; x < groups.size(); ++x ) - updateNames( groups[ x ], allDictionaries ); + for ( auto & group : groups ) + updateNames( group, allDictionaries ); } void updateNames( Config::Class & cfg, vector< sptr< Dictionary::Class > > const & allDictionaries ) diff --git a/src/instances.hh b/src/instances.hh index fdf7b2b78..56a87d28e 100644 --- a/src/instances.hh +++ b/src/instances.hh @@ -33,9 +33,9 @@ struct Group Config::Group const & inactiveGroup ); /// Creates an empty group. - explicit Group( QString const & name_ ); + explicit Group( QString name_ ); - Group( unsigned id, QString const & name_ ); + Group( unsigned id, QString name_ ); /// Makes the configuration group from the current contents. Config::Group makeConfigGroup(); diff --git a/src/ui/dictheadwords.cc b/src/ui/dictheadwords.cc index b10f24138..e39571975 100644 --- a/src/ui/dictheadwords.cc +++ b/src/ui/dictheadwords.cc @@ -168,11 +168,28 @@ void DictHeadwords::savePos() bool DictHeadwords::eventFilter( QObject * obj, QEvent * ev ) { if ( obj == ui.headersListView && ev->type() == QEvent::KeyPress ) { - QKeyEvent * kev = static_cast< QKeyEvent * >( ev ); + auto * kev = dynamic_cast< QKeyEvent * >( ev ); if ( kev->key() == Qt::Key_Return || kev->key() == Qt::Key_Enter ) { itemClicked( ui.headersListView->currentIndex() ); return true; } + else if ( kev->key() == Qt::Key_Up ) { + auto index = ui.headersListView->currentIndex(); + if ( index.row() == 0 ) + return true; + auto preIndex = ui.headersListView->model()->index( index.row() - 1, index.column() ); + ui.headersListView->setCurrentIndex( preIndex ); + return true; + } + else if ( kev->key() == Qt::Key_Down ) { + auto index = ui.headersListView->currentIndex(); + //last row. + if ( index.row() == ui.headersListView->model()->rowCount() - 1 ) + return true; + auto preIndex = ui.headersListView->model()->index( index.row() + 1, index.column() ); + ui.headersListView->setCurrentIndex( preIndex ); + return true; + } } return QDialog::eventFilter( obj, ev ); } @@ -278,12 +295,11 @@ void DictHeadwords::showHeadwordsNumber() .arg( QString::number( model->totalCount() ), QString::number( proxy->rowCount() ) ) ); } -// TODO , the ui and the code mixed together , this is not the right way to do this. need future refactor void DictHeadwords::loadAllSortedWords( QProgressDialog & progress ) { const int headwordsNumber = model->totalCount(); - QMutexLocker _( &mutex ); + QMutexLocker const _( &mutex ); if ( sortedWords.isEmpty() ) { QSet< QString > allHeadwords; @@ -330,10 +346,10 @@ void DictHeadwords::saveHeadersToFile() exportPath = QDir::homePath(); } - QString fileName = QFileDialog::getSaveFileName( this, - tr( "Save headwords to file" ), - exportPath, - tr( "Text files (*.txt);;All files (*.*)" ) ); + QString const fileName = QFileDialog::getSaveFileName( this, + tr( "Save headwords to file" ), + exportPath, + tr( "Text files (*.txt);;All files (*.*)" ) ); if ( fileName.size() == 0 ) return; diff --git a/src/ui/mainwindow.cc b/src/ui/mainwindow.cc index 73199837b..3360ecbd6 100644 --- a/src/ui/mainwindow.cc +++ b/src/ui/mainwindow.cc @@ -2422,7 +2422,10 @@ void MainWindow::translateInputFinished( bool checkModifiers ) respondToTranslationRequest( word, checkModifiers ); } -void MainWindow::respondToTranslationRequest( QString const & word, bool checkModifiers, QString const & scrollTo ) +void MainWindow::respondToTranslationRequest( QString const & word, + bool checkModifiers, + QString const & scrollTo, + bool focus ) { if ( !word.isEmpty() ) { Qt::KeyboardModifiers mods = QApplication::keyboardModifiers(); @@ -2436,7 +2439,9 @@ void MainWindow::respondToTranslationRequest( QString const & word, bool checkMo activateWindow(); } - focusArticleView(); + if ( focus ) { + focusArticleView(); + } } } @@ -3618,7 +3623,7 @@ void MainWindow::headwordReceived( const QString & word, const QString & ID ) { toggleMainWindow( true ); setInputLineText( word, WildcardPolicy::EscapeWildcards, NoPopupChange ); - respondToTranslationRequest( word, false, ArticleView::scrollToFromDictionaryId( ID ) ); + respondToTranslationRequest( word, false, ArticleView::scrollToFromDictionaryId( ID ), false ); } void MainWindow::updateFavoritesMenu() diff --git a/src/ui/mainwindow.hh b/src/ui/mainwindow.hh index 60c0293f8..190830f8e 100644 --- a/src/ui/mainwindow.hh +++ b/src/ui/mainwindow.hh @@ -249,7 +249,10 @@ private: QString unescapeTabHeader( QString const & header ); - void respondToTranslationRequest( QString const & word, bool checkModifiers, QString const & scrollTo = QString() ); + void respondToTranslationRequest( QString const & word, + bool checkModifiers, + QString const & scrollTo = QString(), + bool focus = true ); void updateSuggestionList(); void updateSuggestionList( QString const & text ); From e312545204ba82133921376d1493e1eb0be8c31d Mon Sep 17 00:00:00 2001 From: YiFang Xiao Date: Wed, 24 Jan 2024 22:55:34 +0800 Subject: [PATCH 02/67] fix: possible crash on macos --- src/ui/stylescombobox.cc | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/ui/stylescombobox.cc b/src/ui/stylescombobox.cc index f14d06a4e..ddfb1342c 100644 --- a/src/ui/stylescombobox.cc +++ b/src/ui/stylescombobox.cc @@ -8,7 +8,6 @@ StylesComboBox::StylesComboBox( QWidget * parent ): QComboBox( parent ) { fill(); - setVisible( count() > 1 ); } void StylesComboBox::fill() @@ -19,26 +18,25 @@ void StylesComboBox::fill() if ( !stylesDir.isEmpty() ) { QDir dir( stylesDir ); QStringList styles = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot, QDir::LocaleAware ); - addItems( styles ); + if ( !styles.isEmpty() ) { + addItems( styles ); + } } + + setVisible( count() > 1 ); } void StylesComboBox::setCurrentStyle( QString const & style ) { - int nom = 0; - if ( !style.isEmpty() ) { - for ( int i = 1; i < count(); i++ ) - if ( style.compare( itemText( i ) ) == 0 ) { - nom = i; - break; - } + int nom = findText( style ); + if ( nom > -1 ) { + setCurrentIndex( nom ); } - setCurrentIndex( nom ); } QString StylesComboBox::getCurrentStyle() const { if ( currentIndex() == 0 ) - return QString(); + return {}; return itemText( currentIndex() ); } From 2acf36d42168482491eb5621d10eb7a39f7df0cd Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Thu, 25 Jan 2024 00:59:13 -0500 Subject: [PATCH 03/67] fix: correctly uses CMake's FindX11 --- CMake_Unix.cmake | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CMake_Unix.cmake b/CMake_Unix.cmake index 431a3826a..608b0ae6c 100644 --- a/CMake_Unix.cmake +++ b/CMake_Unix.cmake @@ -12,9 +12,13 @@ target_include_directories(${GOLDENDICT} PRIVATE if (LINUX OR BSD) find_package(X11 REQUIRED) - pkg_check_modules(LIBXTST IMPORTED_TARGET xtst) - target_compile_definitions(${GOLDENDICT} PUBLIC HAVE_X11) - target_link_libraries(${GOLDENDICT} PRIVATE X11 PkgConfig::LIBXTST) + if (X11_FOUND AND X11_Xtst_FOUND) + target_compile_definitions(${GOLDENDICT} PUBLIC HAVE_X11) + target_link_libraries(${GOLDENDICT} PRIVATE ${X11_LIBRARIES} ${X11_Xtst_LIB}) + target_include_directories(${GOLDENDICT} PRIVATE ${X11_INCLUDE_DIR} ${X11_Xtst_INCLUDE_PATH}) + else () + message(FATAL_ERROR "Cannot find X11 and libXtst!") + endif () endif () if (APPLE) From 4304fc9aefcb6b30a8fbe44944b255abf782326a Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Thu, 25 Jan 2024 02:59:45 -0500 Subject: [PATCH 04/67] fix: uses libc iconv on FreeBSD --- CMake_Unix.cmake | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/CMake_Unix.cmake b/CMake_Unix.cmake index 608b0ae6c..b17804f75 100644 --- a/CMake_Unix.cmake +++ b/CMake_Unix.cmake @@ -33,7 +33,6 @@ find_package(PkgConfig REQUIRED) # Provided by Cmake find_package(ZLIB REQUIRED) find_package(BZip2 REQUIRED) -find_package(Iconv REQUIRED) # Consider all PkgConfig dependencies as one @@ -50,8 +49,19 @@ target_link_libraries(${GOLDENDICT} PRIVATE PkgConfig::PKGCONFIG_DEPS BZip2::BZip2 ZLIB::ZLIB - Iconv::Iconv - ) +) + +# On FreeBSD, there are two iconv, libc iconv & GNU libiconv. +# The system one is good enough, the following is a workaround to use libc iconv on freeBSD. +if (BSD STREQUAL "FreeBSD") + # Simply do nothing. libc includes iconv on freeBSD. + # LIBICONV_PLUG is a magic word to turn /usr/include/local/inconv.h, which belong to GNU libiconv, into normal iconv.h + # Same hack used by SDL https://github.com/libsdl-org/SDL/blob/d6ebbc2fa4abdbe0bd53d0ce8804a492ecb042b9/src/stdlib/SDL_iconv.c#L27-L28 + target_compile_definitions(${GOLDENDICT} PUBLIC LIBICONV_PLUG) +else () + find_package(Iconv REQUIRED) + target_link_libraries(${GOLDENDICT} PRIVATE Iconv::Iconv) +endif () if (WITH_FFMPEG_PLAYER) pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET From 853130ddb7bd3b4c047e16e8d98625a102355907 Mon Sep 17 00:00:00 2001 From: Xu Jiyong Date: Fri, 26 Jan 2024 07:56:17 +0800 Subject: [PATCH 05/67] opt: add the language variant option for wikipedia dictionaries (#1374) * opt: add the language variant option for wikipedia dictionaries * fix:default value for 'lang_' parameter * [autofix.ci] apply automated fixes * fix: Should have been 'https' for the wikipedia queries. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- locale/crowdin.ts | 4 ++++ src/config.cc | 29 +++++++++++++++++++++++++++++ src/config.hh | 14 +++++++++++--- src/dict/mediawiki.cc | 18 ++++++++++++++---- src/dict/sources.cc | 13 ++++++++++++- 5 files changed, 70 insertions(+), 8 deletions(-) diff --git a/locale/crowdin.ts b/locale/crowdin.ts index 28a1375c2..51510c92e 100644 --- a/locale/crowdin.ts +++ b/locale/crowdin.ts @@ -2755,6 +2755,10 @@ To find '*', '?', '[', ']' symbols use & Icon 图标 + + Language Variant + 语言变体 + MultimediaAudioPlayer diff --git a/src/config.cc b/src/config.cc index f103d9b2f..47c7dbd9b 100644 --- a/src/config.cc +++ b/src/config.cc @@ -354,6 +354,30 @@ MediaWikis makeDefaultMediaWikis( bool enable ) MediaWiki( "f3b4ec8531e52ddf5b10d21e4577a7a2", "Greek Wikipedia", "https://el.wikipedia.org/w", false, "" ) ); mw.push_back( MediaWiki( "5d45232075d06e002dea72fe3e137da1", "Greek Wiktionary", "https://el.wiktionary.org/w", false, "" ) ); + mw.push_back( MediaWiki( "c015d60c4949ad75b5b7069c2ff6dc2c", + "traditional Chinese Wikipedia", + "https://zh.wikipedia.org/w", + false, + "", + "zh-hant" ) ); + mw.push_back( MediaWiki( "d50828ad6e115bc9d3421b6821543108", + "traditional Chinese Wiktionary", + "https://zh.wiktionary.org/w", + false, + "", + "zh-hant" ) ); + mw.push_back( MediaWiki( "438b17b48cbda1a22d317fea37ec3110", + "Simplified Chinese Wikipedia", + "https://zh.wikipedia.org/w", + false, + "", + "zh-hans" ) ); + mw.push_back( MediaWiki( "b68b9fb71b5a8c766cc7a5ea8237fc6b", + "Simplified Chinese Wiktionary", + "https://zh.wiktionary.org/w", + false, + "", + "zh-hans" ) ); return mw; } @@ -760,6 +784,7 @@ Class load() w.url = mw.attribute( "url" ); w.enabled = ( mw.attribute( "enabled" ) == "1" ); w.icon = mw.attribute( "icon" ); + w.lang = mw.attribute( "lang" ); c.mediawikis.push_back( w ); } @@ -1527,6 +1552,10 @@ void save( Class const & c ) QDomAttr icon = dd.createAttribute( "icon" ); icon.setValue( mediawiki.icon ); mw.setAttributeNode( icon ); + + QDomAttr lang = dd.createAttribute( "lang" ); + lang.setValue( mediawiki.lang ); + mw.setAttributeNode( lang ); } } diff --git a/src/config.hh b/src/config.hh index a809f2724..ed057c57a 100644 --- a/src/config.hh +++ b/src/config.hh @@ -455,24 +455,32 @@ struct MediaWiki QString id, name, url; bool enabled; QString icon; + QString lang; MediaWiki(): enabled( false ) { } - MediaWiki( QString const & id_, QString const & name_, QString const & url_, bool enabled_, QString const & icon_ ): + MediaWiki( QString const & id_, + QString const & name_, + QString const & url_, + bool enabled_, + QString const & icon_, + QString const & lang_ = "" ): id( id_ ), name( name_ ), url( url_ ), enabled( enabled_ ), - icon( icon_ ) + icon( icon_ ), + lang( lang_ ) { } bool operator==( MediaWiki const & other ) const { - return id == other.id && name == other.name && url == other.url && enabled == other.enabled && icon == other.icon; + return id == other.id && name == other.name && url == other.url && enabled == other.enabled && icon == other.icon + && lang == other.lang; } }; diff --git a/src/dict/mediawiki.cc b/src/dict/mediawiki.cc index d7ccdf431..81905ba5f 100644 --- a/src/dict/mediawiki.cc +++ b/src/dict/mediawiki.cc @@ -26,7 +26,7 @@ namespace { class MediaWikiDictionary: public Dictionary::Class { string name; - QString url, icon; + QString url, icon, lang; QNetworkAccessManager & netMgr; quint32 langId; @@ -36,11 +36,13 @@ class MediaWikiDictionary: public Dictionary::Class string const & name_, QString const & url_, QString const & icon_, + QString const & lang_, QNetworkAccessManager & netMgr_ ): Dictionary::Class( id, vector< string >() ), name( name_ ), url( url_ ), icon( icon_ ), + lang( lang_ ), netMgr( netMgr_ ), langId( 0 ) { @@ -132,7 +134,7 @@ class MediaWikiWordSearchRequest: public MediaWikiWordSearchRequestSlots public: - MediaWikiWordSearchRequest( wstring const &, QString const & url, QNetworkAccessManager & mgr ); + MediaWikiWordSearchRequest( wstring const &, QString const & url, QString const & lang, QNetworkAccessManager & mgr ); ~MediaWikiWordSearchRequest(); @@ -145,6 +147,7 @@ class MediaWikiWordSearchRequest: public MediaWikiWordSearchRequestSlots MediaWikiWordSearchRequest::MediaWikiWordSearchRequest( wstring const & str, QString const & url, + QString const & lang, QNetworkAccessManager & mgr ): isCancelling( false ) { @@ -154,6 +157,7 @@ MediaWikiWordSearchRequest::MediaWikiWordSearchRequest( wstring const & str, GlobalBroadcaster::instance()->addWhitelist( reqUrl.host() ); Utils::Url::addQueryItem( reqUrl, "apprefix", QString::fromStdU32String( str ).replace( '+', "%2B" ) ); + Utils::Url::addQueryItem( reqUrl, "lang", lang ); QNetworkRequest req( reqUrl ); //millseconds. @@ -376,12 +380,14 @@ class MediaWikiArticleRequest: public MediaWikiDataRequestSlots typedef std::list< std::pair< QNetworkReply *, bool > > NetReplies; NetReplies netReplies; QString url; + QString lang; public: MediaWikiArticleRequest( wstring const & word, vector< wstring > const & alts, QString const & url, + QString const & lang, QNetworkAccessManager & mgr, Class * dictPtr_ ); @@ -425,9 +431,11 @@ void MediaWikiArticleRequest::cancel() MediaWikiArticleRequest::MediaWikiArticleRequest( wstring const & str, vector< wstring > const & alts, QString const & url_, + QString const & lang_, QNetworkAccessManager & mgr, Class * dictPtr_ ): url( url_ ), + lang( lang_ ), dictPtr( dictPtr_ ) { connect( &mgr, @@ -449,6 +457,7 @@ void MediaWikiArticleRequest::addQuery( QNetworkAccessManager & mgr, wstring con QUrl reqUrl( url + "/api.php?action=parse&prop=text|revid|sections&format=xml&redirects" ); Utils::Url::addQueryItem( reqUrl, "page", QString::fromStdU32String( str ).replace( '+', "%2B" ) ); + Utils::Url::addQueryItem( reqUrl, "variant", lang ); QNetworkRequest req( reqUrl ); //millseconds. req.setTransferTimeout( 3000 ); @@ -690,7 +699,7 @@ sptr< WordSearchRequest > MediaWikiDictionary::prefixMatch( wstring const & word return std::make_shared< WordSearchRequestInstant >(); } else - return std::make_shared< MediaWikiWordSearchRequest >( word, url, netMgr ); + return std::make_shared< MediaWikiWordSearchRequest >( word, url, lang, netMgr ); } sptr< DataRequest > @@ -703,7 +712,7 @@ MediaWikiDictionary::getArticle( wstring const & word, vector< wstring > const & return std::make_shared< DataRequestInstant >( false ); } else - return std::make_shared< MediaWikiArticleRequest >( word, alts, url, netMgr, this ); + return std::make_shared< MediaWikiArticleRequest >( word, alts, url, lang, netMgr, this ); } } // namespace @@ -720,6 +729,7 @@ makeDictionaries( Dictionary::Initializing &, Config::MediaWikis const & wikis, wiki.name.toUtf8().data(), wiki.url, wiki.icon, + wiki.lang, mgr ) ); } diff --git a/src/dict/sources.cc b/src/dict/sources.cc index 3acdec4c4..10ffd2faa 100644 --- a/src/dict/sources.cc +++ b/src/dict/sources.cc @@ -49,6 +49,7 @@ Sources::Sources( QWidget * parent, Config::Class const & cfg ): ui.mediaWikis->resizeColumnToContents( 1 ); ui.mediaWikis->resizeColumnToContents( 2 ); ui.mediaWikis->resizeColumnToContents( 3 ); + ui.mediaWikis->resizeColumnToContents( 4 ); ui.webSites->setTabKeyNavigation( true ); ui.webSites->setModel( &webSitesModel ); @@ -429,6 +430,8 @@ void MediaWikisModel::addNewWiki() w.url = "http://"; + w.lang = ""; + beginInsertRows( QModelIndex(), mediawikis.size(), mediawikis.size() ); mediawikis.push_back( w ); endInsertRows(); @@ -471,7 +474,7 @@ int MediaWikisModel::columnCount( QModelIndex const & parent ) const if ( parent.isValid() ) return 0; else - return 4; + return 5; } QVariant MediaWikisModel::headerData( int section, Qt::Orientation /*orientation*/, int role ) const @@ -486,6 +489,8 @@ QVariant MediaWikisModel::headerData( int section, Qt::Orientation /*orientation return tr( "Address" ); case 3: return tr( "Icon" ); + case 4: + return tr( "Language Variant" ); default: return QVariant(); } @@ -506,6 +511,8 @@ QVariant MediaWikisModel::data( QModelIndex const & index, int role ) const return mediawikis[ index.row() ].url; case 3: return mediawikis[ index.row() ].icon; + case 4: + return mediawikis[ index.row() ].lang; default: return QVariant(); } @@ -547,6 +554,10 @@ bool MediaWikisModel::setData( QModelIndex const & index, const QVariant & value mediawikis[ index.row() ].icon = value.toString(); dataChanged( index, index ); return true; + case 4: + mediawikis[ index.row() ].lang = value.toString(); + dataChanged( index, index ); + return true; default: return false; } From 9d34a6a31637f0459eebfe6e7a29c386d28dc854 Mon Sep 17 00:00:00 2001 From: xiaoyifang <105986+xiaoyifang@users.noreply.github.com> Date: Fri, 26 Jan 2024 08:20:23 +0800 Subject: [PATCH 06/67] New Crowdin updates (#1384) * New translations Chinese Simplified from Crowdin * New translations French from Crowdin * New translations Spanish from Crowdin * New translations Belarusian from Crowdin * New translations Bulgarian from Crowdin * New translations Czech from Crowdin * New translations German from Crowdin * New translations Greek from Crowdin * New translations Finnish from Crowdin * New translations Italian from Crowdin * New translations Japanese from Crowdin * New translations Korean from Crowdin * New translations Lithuanian from Crowdin * New translations Macedonian from Crowdin * New translations Dutch from Crowdin * New translations Polish from Crowdin * New translations Portuguese from Crowdin * New translations Russian from Crowdin * New translations Slovak from Crowdin * New translations Albanian from Crowdin * New translations Serbian (Cyrillic) from Crowdin * New translations Swedish from Crowdin * New translations Turkish from Crowdin * New translations Ukrainian from Crowdin * New translations Chinese Traditional from Crowdin * New translations Vietnamese from Crowdin * New translations Portuguese, Brazilian from Crowdin * New translations Persian from Crowdin * New translations Spanish, Argentina from Crowdin * New translations Hindi from Crowdin * New translations Esperanto from Crowdin * New translations Lojban from Crowdin * New translations German, Switzerland from Crowdin * New translations Spanish, Bolivia from Crowdin * New translations Tajik from Crowdin * New translations Quechua from Crowdin * New translations Aymara from Crowdin * New translations Arabic, Saudi Arabia from Crowdin * New translations Turkmen from Crowdin * New translations Interlingue from Crowdin --- locale/ar_SA.ts | 4 ++++ locale/ay_BO.ts | 4 ++++ locale/be_BY.ts | 4 ++++ locale/bg_BG.ts | 4 ++++ locale/cs_CZ.ts | 4 ++++ locale/de_CH.ts | 4 ++++ locale/de_DE.ts | 4 ++++ locale/el_GR.ts | 4 ++++ locale/eo_UY.ts | 4 ++++ locale/es_AR.ts | 4 ++++ locale/es_BO.ts | 4 ++++ locale/es_ES.ts | 4 ++++ locale/fa_IR.ts | 4 ++++ locale/fi_FI.ts | 4 ++++ locale/fr_FR.ts | 4 ++++ locale/hi_IN.ts | 4 ++++ locale/ie_001.ts | 4 ++++ locale/it_IT.ts | 4 ++++ locale/ja_JP.ts | 4 ++++ locale/jbo_EN.ts | 4 ++++ locale/ko_KR.ts | 4 ++++ locale/lt_LT.ts | 4 ++++ locale/mk_MK.ts | 4 ++++ locale/nl_NL.ts | 4 ++++ locale/pl_PL.ts | 4 ++++ locale/pt_BR.ts | 4 ++++ locale/pt_PT.ts | 4 ++++ locale/qu_PE.ts | 4 ++++ locale/ru_RU.ts | 4 ++++ locale/sk_SK.ts | 4 ++++ locale/sq_AL.ts | 4 ++++ locale/sr_SP.ts | 4 ++++ locale/sv_SE.ts | 4 ++++ locale/tg_TJ.ts | 4 ++++ locale/tk_TM.ts | 4 ++++ locale/tr_TR.ts | 4 ++++ locale/uk_UA.ts | 4 ++++ locale/vi_VN.ts | 4 ++++ locale/zh_CN.ts | 4 ++++ locale/zh_TW.ts | 4 ++++ 40 files changed, 160 insertions(+) diff --git a/locale/ar_SA.ts b/locale/ar_SA.ts index 0e86847ab..f8f667f30 100644 --- a/locale/ar_SA.ts +++ b/locale/ar_SA.ts @@ -2754,6 +2754,10 @@ To find '*', '?', '[', ']' symbols use & Icon الرّمز + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/ay_BO.ts b/locale/ay_BO.ts index ddefd4c13..b0d5496e9 100644 --- a/locale/ay_BO.ts +++ b/locale/ay_BO.ts @@ -2823,6 +2823,10 @@ Añadir la pestaña actual a favoritos Icon Salta + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/be_BY.ts b/locale/be_BY.ts index 7d5be811c..a06690938 100644 --- a/locale/be_BY.ts +++ b/locale/be_BY.ts @@ -2755,6 +2755,10 @@ To find '*', '?', '[', ']' symbols use & Icon Значок + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/bg_BG.ts b/locale/bg_BG.ts index f16f364a1..e606d7900 100644 --- a/locale/bg_BG.ts +++ b/locale/bg_BG.ts @@ -2756,6 +2756,10 @@ To find '*', '?', '[', ']' symbols use & Icon Иконка + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/cs_CZ.ts b/locale/cs_CZ.ts index 153f07264..6216e8ea6 100644 --- a/locale/cs_CZ.ts +++ b/locale/cs_CZ.ts @@ -2756,6 +2756,10 @@ Pro zjištění '*', '?', '[', ']' symbo Icon Ikona + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/de_CH.ts b/locale/de_CH.ts index c49ce6dac..38c065f1a 100644 --- a/locale/de_CH.ts +++ b/locale/de_CH.ts @@ -2753,6 +2753,10 @@ Um folgende Symbole zu finden '*', '?', '[', &apos Icon Symbol + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/de_DE.ts b/locale/de_DE.ts index faf781a44..d8369885c 100644 --- a/locale/de_DE.ts +++ b/locale/de_DE.ts @@ -2755,6 +2755,10 @@ Um '*', 'zu finden?', '[', ']' Symbole v Icon Symbol + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/el_GR.ts b/locale/el_GR.ts index 1229a3a57..ceb8a3184 100644 --- a/locale/el_GR.ts +++ b/locale/el_GR.ts @@ -2757,6 +2757,10 @@ To find '*', '?', '[', ']' symbols use & Icon Εικονίδιο + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/eo_UY.ts b/locale/eo_UY.ts index 1f5423991..da003df4c 100644 --- a/locale/eo_UY.ts +++ b/locale/eo_UY.ts @@ -2756,6 +2756,10 @@ Por trovi '*', '?', '[', ']' simboloj uz Icon Bildsimbolo + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/es_AR.ts b/locale/es_AR.ts index 8f81d5191..b64b23c86 100644 --- a/locale/es_AR.ts +++ b/locale/es_AR.ts @@ -2756,6 +2756,10 @@ To find '*', '?', '[', ']' symbols use & Icon Ícono + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/es_BO.ts b/locale/es_BO.ts index 4e519ba69..6019371b8 100644 --- a/locale/es_BO.ts +++ b/locale/es_BO.ts @@ -2756,6 +2756,10 @@ To find '*', '?', '[', ']' symbols use & Icon Icon + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/es_ES.ts b/locale/es_ES.ts index f2e8722fd..f589b661f 100644 --- a/locale/es_ES.ts +++ b/locale/es_ES.ts @@ -2756,6 +2756,10 @@ Para encontrar '*', '?', '[', ']' símbo Icon Icono + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/fa_IR.ts b/locale/fa_IR.ts index 6dcb49a6e..94fc86a90 100644 --- a/locale/fa_IR.ts +++ b/locale/fa_IR.ts @@ -2755,6 +2755,10 @@ To find '*', '?', '[', ']' symbols use & Icon نشانه + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/fi_FI.ts b/locale/fi_FI.ts index f244ac145..717618e34 100644 --- a/locale/fi_FI.ts +++ b/locale/fi_FI.ts @@ -2756,6 +2756,10 @@ Löytääksesi '*', '?', '[', ']' tunnus Icon Ikoni + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/fr_FR.ts b/locale/fr_FR.ts index 30ed50827..39cc9de40 100644 --- a/locale/fr_FR.ts +++ b/locale/fr_FR.ts @@ -2755,6 +2755,10 @@ Pour rechercher les symboles '*', '?', '[', ' Icon Icône + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/hi_IN.ts b/locale/hi_IN.ts index 0ba15eb58..0c40eebd9 100644 --- a/locale/hi_IN.ts +++ b/locale/hi_IN.ts @@ -2755,6 +2755,10 @@ Pour rechercher les symboles '*', '?', '[', ' Icon चित्रक + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/ie_001.ts b/locale/ie_001.ts index c9730e6bd..aff33702c 100644 --- a/locale/ie_001.ts +++ b/locale/ie_001.ts @@ -2756,6 +2756,10 @@ To find '*', '?', '[', ']' symbols use & Icon Icone + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/it_IT.ts b/locale/it_IT.ts index f3b11e934..218c9591e 100644 --- a/locale/it_IT.ts +++ b/locale/it_IT.ts @@ -2757,6 +2757,10 @@ Per utilizzare nelle ricerche i caratteri '*', '?', '[& Icon Icona + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/ja_JP.ts b/locale/ja_JP.ts index 37159bfcd..6db51a7be 100644 --- a/locale/ja_JP.ts +++ b/locale/ja_JP.ts @@ -2756,6 +2756,10 @@ To find '*', '?', '[', ']' symbols use & Icon アイコン + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/jbo_EN.ts b/locale/jbo_EN.ts index 65f8c432c..075a66dbd 100644 --- a/locale/jbo_EN.ts +++ b/locale/jbo_EN.ts @@ -2756,6 +2756,10 @@ To find '*', '?', '[', ']' symbols use & Icon pixra + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/ko_KR.ts b/locale/ko_KR.ts index 0da5ab0b6..5e668068b 100644 --- a/locale/ko_KR.ts +++ b/locale/ko_KR.ts @@ -2756,6 +2756,10 @@ To find '*', '?', '[', ']' symbols use & Icon 아이콘 + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/lt_LT.ts b/locale/lt_LT.ts index dc8d8f06d..2340cdc28 100644 --- a/locale/lt_LT.ts +++ b/locale/lt_LT.ts @@ -2756,6 +2756,10 @@ Norėdami rasti „*“, „?“, „[“, „]“ simbolius, atitinkamai įvesk Icon Ženkliukas + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/mk_MK.ts b/locale/mk_MK.ts index ae0ec44e6..9e3683689 100644 --- a/locale/mk_MK.ts +++ b/locale/mk_MK.ts @@ -2757,6 +2757,10 @@ To find '*', '?', '[', ']' symbols use & Icon Икона + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/nl_NL.ts b/locale/nl_NL.ts index f52c9493b..82b6a9138 100644 --- a/locale/nl_NL.ts +++ b/locale/nl_NL.ts @@ -2756,6 +2756,10 @@ Om '*'te vinden, '?', '[', ']' symbolen Icon Pictogram + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/pl_PL.ts b/locale/pl_PL.ts index 4af2c5108..a7203f68f 100644 --- a/locale/pl_PL.ts +++ b/locale/pl_PL.ts @@ -2756,6 +2756,10 @@ Aby odnaleźć symbole „*”, „?”, „[” i „]”, należy użyć odpow Icon Ikona + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/pt_BR.ts b/locale/pt_BR.ts index dd3bb8881..a5604267f 100644 --- a/locale/pt_BR.ts +++ b/locale/pt_BR.ts @@ -2756,6 +2756,10 @@ Para encontrar os símbolos '*', '?', '[', '] Icon Ícone + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/pt_PT.ts b/locale/pt_PT.ts index 19cc7119b..4431e477c 100644 --- a/locale/pt_PT.ts +++ b/locale/pt_PT.ts @@ -2756,6 +2756,10 @@ Para encontrar '*', '?', '[', ']' símbo Icon Ícone + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/qu_PE.ts b/locale/qu_PE.ts index 4b31bd783..86fbc5e9b 100644 --- a/locale/qu_PE.ts +++ b/locale/qu_PE.ts @@ -2756,6 +2756,10 @@ To find '*', '?', '[', ']' symbols use & Icon Icon + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/ru_RU.ts b/locale/ru_RU.ts index 3c43011e9..b329d0ec3 100644 --- a/locale/ru_RU.ts +++ b/locale/ru_RU.ts @@ -2758,6 +2758,10 @@ To find '*', '?', '[', ']' symbols use & Icon Значок + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/sk_SK.ts b/locale/sk_SK.ts index cffe81d9d..ebf9ce1af 100644 --- a/locale/sk_SK.ts +++ b/locale/sk_SK.ts @@ -2757,6 +2757,10 @@ Pre vyhľadanie znakov '*', '?', '[', ']&apos Icon Ikona + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/sq_AL.ts b/locale/sq_AL.ts index 5dc236fcb..3c0800bd8 100644 --- a/locale/sq_AL.ts +++ b/locale/sq_AL.ts @@ -2755,6 +2755,10 @@ Për të gjetur '*', '?', '[', ']' simbo Icon Ikona + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/sr_SP.ts b/locale/sr_SP.ts index 627d97ac1..7134e5cc0 100644 --- a/locale/sr_SP.ts +++ b/locale/sr_SP.ts @@ -2757,6 +2757,10 @@ To find '*', '?', '[', ']' symbols use & Icon Икона + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/sv_SE.ts b/locale/sv_SE.ts index 9200975b7..818118fb4 100644 --- a/locale/sv_SE.ts +++ b/locale/sv_SE.ts @@ -2756,6 +2756,10 @@ För att hitta '*', '?', '[', ']' symbol Icon Ikon + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/tg_TJ.ts b/locale/tg_TJ.ts index b82441ed0..a9a6d49e0 100644 --- a/locale/tg_TJ.ts +++ b/locale/tg_TJ.ts @@ -2756,6 +2756,10 @@ To find '*', '?', '[', ']' symbols use & Icon Аломат + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/tk_TM.ts b/locale/tk_TM.ts index c606fdc44..9f65aa1b4 100644 --- a/locale/tk_TM.ts +++ b/locale/tk_TM.ts @@ -2755,6 +2755,10 @@ To find '*', '?', '[', ']' symbols use & Icon Nyşan + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/tr_TR.ts b/locale/tr_TR.ts index d3bfdb9e5..441a8a5f8 100644 --- a/locale/tr_TR.ts +++ b/locale/tr_TR.ts @@ -2756,6 +2756,10 @@ To find '*', '?', '[', ']' symbols use & Icon Simge + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/uk_UA.ts b/locale/uk_UA.ts index 1b340f150..0ec73d1b3 100644 --- a/locale/uk_UA.ts +++ b/locale/uk_UA.ts @@ -2757,6 +2757,10 @@ To find '*', '?', '[', ']' symbols use & Icon Наличка + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/vi_VN.ts b/locale/vi_VN.ts index 0edfbe0c1..5143a2784 100644 --- a/locale/vi_VN.ts +++ b/locale/vi_VN.ts @@ -2755,6 +2755,10 @@ To find '*', '?', '[', ']' symbols use & Icon Biểu tượng + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/zh_CN.ts b/locale/zh_CN.ts index 27f0aa814..7d3f9d656 100644 --- a/locale/zh_CN.ts +++ b/locale/zh_CN.ts @@ -2755,6 +2755,10 @@ To find '*', '?', '[', ']' symbols use & Icon 图标 + + Language Variant + Language Variant + MultimediaAudioPlayer diff --git a/locale/zh_TW.ts b/locale/zh_TW.ts index 740a9e3b6..5a8096e94 100644 --- a/locale/zh_TW.ts +++ b/locale/zh_TW.ts @@ -2758,6 +2758,10 @@ To find '*', '?', '[', ']' symbols use & Icon 圖示 + + Language Variant + Language Variant + MultimediaAudioPlayer From 0596ed5e450a5b11fe6b11d6031782e23ed7118e Mon Sep 17 00:00:00 2001 From: YiFang Xiao Date: Fri, 26 Jan 2024 15:50:01 +0800 Subject: [PATCH 07/67] opt: remove debug info --- src/article_maker.cc | 9 --------- src/dict/dsl.cc | 4 +--- src/dict/hunspell.cc | 3 --- src/ui/mainwindow.cc | 2 -- 4 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/article_maker.cc b/src/article_maker.cc index 3c32bedac..b531a3bcd 100644 --- a/src/article_maker.cc +++ b/src/article_maker.cc @@ -486,9 +486,6 @@ void ArticleRequest::altSearchFinished() } if ( altSearches.empty() ) { -#ifdef QT_DEBUG - qDebug( "alts finished" ); -#endif // They all've finished! Now we can look up bodies @@ -496,12 +493,6 @@ void ArticleRequest::altSearchFinished() vector< wstring > altsVector( alts.begin(), alts.end() ); -#ifdef QT_DEBUG - for ( const auto & x : altsVector ) { - qDebug() << "Alt:" << QString::fromStdU32String( x ); - } -#endif - wstring wordStd = gd::toWString( word ); if ( activeDicts.size() <= 1 ) diff --git a/src/dict/dsl.cc b/src/dict/dsl.cc index 01cc75aa7..61b63679e 100644 --- a/src/dict/dsl.cc +++ b/src/dict/dsl.cc @@ -1887,9 +1887,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f if ( isDslWs( curString[ 0 ] ) ) break; // No more headwords -#ifdef QT_DEBUG - qDebug() << "Alt headword" << QString::fromStdU32String( curString ); -#endif + qDebug() << "dsl Alt headword" << QString::fromStdU32String( curString ); processUnsortedParts( curString, true ); expandTildes( curString, allEntryWords.front() ); diff --git a/src/dict/hunspell.cc b/src/dict/hunspell.cc index a2f45e41b..165ba86ec 100644 --- a/src/dict/hunspell.cc +++ b/src/dict/hunspell.cc @@ -411,9 +411,6 @@ QVector< wstring > suggest( wstring & word, QMutex & hunspellMutex, Hunspell & h if ( Folding::applySimpleCaseOnly( alt ) != lowercasedWord ) // No point in providing same word { -#ifdef QT_DEBUG - qDebug() << ">>>>>Alt:" << QString::fromStdU32String( alt ); -#endif result.append( alt ); } } diff --git a/src/ui/mainwindow.cc b/src/ui/mainwindow.cc index 3360ecbd6..03f6bf434 100644 --- a/src/ui/mainwindow.cc +++ b/src/ui/mainwindow.cc @@ -1635,9 +1635,7 @@ void MainWindow::updateGroupList() updateDictionaryBar(); -#ifdef QT_DEBUG qDebug() << "Reloading all the tabs..."; -#endif for ( int i = 0; i < ui.tabWidget->count(); ++i ) { ArticleView & view = dynamic_cast< ArticleView & >( *( ui.tabWidget->widget( i ) ) ); From 54808cd03f0d8367ca9af6f3aa7dffa493505dfe Mon Sep 17 00:00:00 2001 From: YiFang Xiao Date: Fri, 26 Jan 2024 16:52:27 +0800 Subject: [PATCH 08/67] fix: ctrl+v in Welcome page will result a wrong search --- src/ui/articleview.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/articleview.cc b/src/ui/articleview.cc index 67eebe453..09de2c87d 100644 --- a/src/ui/articleview.cc +++ b/src/ui/articleview.cc @@ -1886,7 +1886,7 @@ void ArticleView::pasteTriggered() if ( !word.isEmpty() ) { unsigned groupId = getGroup( webview->url() ); - if ( groupId == 0 ) { + if ( groupId == 0 || groupId == Instances::Group::HelpGroupId ) { // We couldn't figure out the group out of the URL, // so let's try the currently selected group. groupId = currentGroupId; From 28924b595367169d72b7f7e36298d01f342be4de Mon Sep 17 00:00:00 2001 From: YiFang Xiao Date: Sat, 27 Jan 2024 10:56:19 +0800 Subject: [PATCH 09/67] opt:refactor code --- src/dict/mdx.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dict/mdx.cc b/src/dict/mdx.cc index bd3f80709..ba5a50766 100644 --- a/src/dict/mdx.cc +++ b/src/dict/mdx.cc @@ -154,7 +154,7 @@ class IndexedMdd: public BtreeIndexing::BtreeIndex if ( links.empty() ) return false; - MdictParser::RecordInfo indexEntry; + MdictParser::RecordInfo indexEntry{}; vector< char > chunk; // QMutexLocker _( &idxMutex ); const char * indexEntryPtr = chunks.getBlock( links[ 0 ].articleOffset, chunk ); @@ -1152,7 +1152,7 @@ void MdxDictionary::loadResourceFile( const wstring & resourceName, vector< char File::loadFromFile( fn, data ); return; } - for ( auto mddResource : mddResources ) { + for ( const auto& mddResource : mddResources ) { if ( mddResource->loadFile( newResourceName, data ) ) break; } From e2120a6d41908fe735a893363fab7caadc696b4a Mon Sep 17 00:00:00 2001 From: YiFang Xiao Date: Sat, 27 Jan 2024 13:07:29 +0800 Subject: [PATCH 10/67] bump alpha version --- .github/workflows/AutoTag.yml | 2 +- .github/workflows/macos-arm-homebrew.yml | 2 +- .github/workflows/macos-homebrew-breakpad.yml | 2 +- .github/workflows/macos-homebrew.yml | 2 +- .github/workflows/ubuntu-6.2.yml | 2 +- .github/workflows/ubuntu.yml | 2 +- .github/workflows/windows-6.x.yml | 2 +- .github/workflows/windows.yml | 2 +- CMakeLists.txt | 2 +- goldendict.pro | 4 ++-- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/AutoTag.yml b/.github/workflows/AutoTag.yml index 9aa37c5d8..55fa525ea 100644 --- a/.github/workflows/AutoTag.yml +++ b/.github/workflows/AutoTag.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest env: - version: 23.12.08 + version: 24.01.27 version-suffix: alpha prerelease: true diff --git a/.github/workflows/macos-arm-homebrew.yml b/.github/workflows/macos-arm-homebrew.yml index 25ff184fc..24e054bcf 100644 --- a/.github/workflows/macos-arm-homebrew.yml +++ b/.github/workflows/macos-arm-homebrew.yml @@ -26,7 +26,7 @@ jobs: qt_arch: [clang_64] env: targetName: GoldenDict - version: 23.12.08 + version: 24.01.27 version-suffix: alpha prerelease: true steps: diff --git a/.github/workflows/macos-homebrew-breakpad.yml b/.github/workflows/macos-homebrew-breakpad.yml index 0fc0d1658..565ade5e5 100644 --- a/.github/workflows/macos-homebrew-breakpad.yml +++ b/.github/workflows/macos-homebrew-breakpad.yml @@ -26,7 +26,7 @@ jobs: qt_arch: [clang_64] env: targetName: GoldenDict - version: 23.12.08 + version: 24.01.27 version-suffix: alpha prerelease: true steps: diff --git a/.github/workflows/macos-homebrew.yml b/.github/workflows/macos-homebrew.yml index 4fd1a5602..edf1a6e23 100644 --- a/.github/workflows/macos-homebrew.yml +++ b/.github/workflows/macos-homebrew.yml @@ -26,7 +26,7 @@ jobs: qt_arch: [clang_64] env: targetName: GoldenDict - version: 23.12.08 + version: 24.01.27 version-suffix: alpha prerelease: true steps: diff --git a/.github/workflows/ubuntu-6.2.yml b/.github/workflows/ubuntu-6.2.yml index 7b20589f1..9e60925c8 100644 --- a/.github/workflows/ubuntu-6.2.yml +++ b/.github/workflows/ubuntu-6.2.yml @@ -24,7 +24,7 @@ jobs: qt_ver: [ 6.6.1 ] qt_arch: [gcc_64] env: - version: 23.12.08 + version: 24.01.27 version-suffix: alpha prerelease: true steps: diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 08858c8b7..f78922757 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -25,7 +25,7 @@ jobs: qt_ver: [5.15.2] qt_arch: [gcc_64] env: - version: 23.12.08 + version: 24.01.27 version-suffix: alpha prerelease: true steps: diff --git a/.github/workflows/windows-6.x.yml b/.github/workflows/windows-6.x.yml index e07381309..a786ef11f 100644 --- a/.github/workflows/windows-6.x.yml +++ b/.github/workflows/windows-6.x.yml @@ -31,7 +31,7 @@ jobs: qt_arch: [win64_msvc2019_64] env: targetName: GoldenDict.exe - version: 23.12.08 + version: 24.01.27 version-suffix: alpha prerelease: true steps: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 26fe11718..b9853a86e 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -27,7 +27,7 @@ jobs: qt_arch: [win64_msvc2019_64] env: targetName: GoldenDict.exe - version: 23.12.08 + version: 24.01.27 version-suffix: alpha prerelease: true # 步骤 diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e3ec7287..9bb368f03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ option(USE_ALTERNATIVE_NAME "Force the name goldendict-ng " OFF) include(FeatureSummary) project(goldendict-ng - VERSION 23.12.08 + VERSION 24.01.27 LANGUAGES CXX C) if (NOT USE_ALTERNATIVE_NAME) diff --git a/goldendict.pro b/goldendict.pro index 1a3891eb6..c5d0140f3 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -1,6 +1,6 @@ TEMPLATE = app TARGET = goldendict -VERSION = 23.12.08 +VERSION = 24.01.27 # Generate version file. We do this here and in a build rule described later. # The build rule is required since qmake isn't run each time the project is @@ -133,7 +133,7 @@ win32 { win32-msvc* { # VS does not recognize 22.number.alpha,cause errors during compilation under MSVC++ - VERSION = 23.12.08 + VERSION = 24.01.27 DEFINES += __WIN32 _CRT_SECURE_NO_WARNINGS contains(QMAKE_TARGET.arch, x86_64) { DEFINES += NOMINMAX __WIN64 From 7400050ae64a3a9b4597c5411cc624fe00450713 Mon Sep 17 00:00:00 2001 From: xiaoyifang <105986+xiaoyifang@users.noreply.github.com> Date: Sat, 27 Jan 2024 16:23:43 +0800 Subject: [PATCH 11/67] New Crowdin updates (#1387) * New translations French from Crowdin * New translations Spanish from Crowdin * New translations Belarusian from Crowdin * New translations Bulgarian from Crowdin * New translations Czech from Crowdin * New translations German from Crowdin * New translations Greek from Crowdin * New translations Finnish from Crowdin * New translations Italian from Crowdin * New translations Japanese from Crowdin * New translations Korean from Crowdin * New translations Lithuanian from Crowdin * New translations Macedonian from Crowdin * New translations Dutch from Crowdin * New translations Polish from Crowdin * New translations Portuguese from Crowdin * New translations Russian from Crowdin * New translations Slovak from Crowdin * New translations Albanian from Crowdin * New translations Serbian (Cyrillic) from Crowdin * New translations Swedish from Crowdin * New translations Turkish from Crowdin * New translations Ukrainian from Crowdin * New translations Chinese Traditional from Crowdin * New translations Vietnamese from Crowdin * New translations Portuguese, Brazilian from Crowdin * New translations Persian from Crowdin * New translations Hindi from Crowdin * New translations Esperanto from Crowdin * New translations Tajik from Crowdin * New translations Quechua from Crowdin * New translations Aymara from Crowdin * New translations Turkmen from Crowdin --- locale/ay_BO.ts | 2 +- locale/be_BY.ts | 2 +- locale/bg_BG.ts | 2 +- locale/cs_CZ.ts | 2 +- locale/de_DE.ts | 2 +- locale/el_GR.ts | 2 +- locale/eo_UY.ts | 2 +- locale/es_ES.ts | 2 +- locale/fa_IR.ts | 2 +- locale/fi_FI.ts | 2 +- locale/fr_FR.ts | 2 +- locale/hi_IN.ts | 2 +- locale/it_IT.ts | 2 +- locale/ja_JP.ts | 2 +- locale/ko_KR.ts | 2 +- locale/lt_LT.ts | 2 +- locale/mk_MK.ts | 2 +- locale/nl_NL.ts | 2 +- locale/pl_PL.ts | 2 +- locale/pt_BR.ts | 2 +- locale/pt_PT.ts | 2 +- locale/qu_PE.ts | 2 +- locale/ru_RU.ts | 2 +- locale/sk_SK.ts | 2 +- locale/sq_AL.ts | 2 +- locale/sr_SP.ts | 2 +- locale/sv_SE.ts | 2 +- locale/tg_TJ.ts | 2 +- locale/tk_TM.ts | 2 +- locale/tr_TR.ts | 2 +- locale/uk_UA.ts | 2 +- locale/vi_VN.ts | 2 +- locale/zh_TW.ts | 2 +- 33 files changed, 33 insertions(+), 33 deletions(-) diff --git a/locale/ay_BO.ts b/locale/ay_BO.ts index b0d5496e9..fac7f9982 100644 --- a/locale/ay_BO.ts +++ b/locale/ay_BO.ts @@ -2825,7 +2825,7 @@ Añadir la pestaña actual a favoritos Language Variant - Language Variant + Aru mayjt’ayaña diff --git a/locale/be_BY.ts b/locale/be_BY.ts index a06690938..534df4e70 100644 --- a/locale/be_BY.ts +++ b/locale/be_BY.ts @@ -2757,7 +2757,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + Варыянт мовы diff --git a/locale/bg_BG.ts b/locale/bg_BG.ts index e606d7900..54de36cb0 100644 --- a/locale/bg_BG.ts +++ b/locale/bg_BG.ts @@ -2758,7 +2758,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + Езиков вариант diff --git a/locale/cs_CZ.ts b/locale/cs_CZ.ts index 6216e8ea6..28e2c1c15 100644 --- a/locale/cs_CZ.ts +++ b/locale/cs_CZ.ts @@ -2758,7 +2758,7 @@ Pro zjištění '*', '?', '[', ']' symbo Language Variant - Language Variant + Jazyková varianta diff --git a/locale/de_DE.ts b/locale/de_DE.ts index d8369885c..68913bea3 100644 --- a/locale/de_DE.ts +++ b/locale/de_DE.ts @@ -2757,7 +2757,7 @@ Um '*', 'zu finden?', '[', ']' Symbole v Language Variant - Language Variant + Sprachvariante diff --git a/locale/el_GR.ts b/locale/el_GR.ts index ceb8a3184..580a25743 100644 --- a/locale/el_GR.ts +++ b/locale/el_GR.ts @@ -2759,7 +2759,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + Παραλλαγή γλώσσας diff --git a/locale/eo_UY.ts b/locale/eo_UY.ts index da003df4c..7dca91895 100644 --- a/locale/eo_UY.ts +++ b/locale/eo_UY.ts @@ -2758,7 +2758,7 @@ Por trovi '*', '?', '[', ']' simboloj uz Language Variant - Language Variant + Lingva Variaĵo diff --git a/locale/es_ES.ts b/locale/es_ES.ts index f589b661f..8dcd77cfe 100644 --- a/locale/es_ES.ts +++ b/locale/es_ES.ts @@ -2758,7 +2758,7 @@ Para encontrar '*', '?', '[', ']' símbo Language Variant - Language Variant + Variante de idioma diff --git a/locale/fa_IR.ts b/locale/fa_IR.ts index 94fc86a90..3d260c5eb 100644 --- a/locale/fa_IR.ts +++ b/locale/fa_IR.ts @@ -2757,7 +2757,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + نوع زبان diff --git a/locale/fi_FI.ts b/locale/fi_FI.ts index 717618e34..b44c6724f 100644 --- a/locale/fi_FI.ts +++ b/locale/fi_FI.ts @@ -2758,7 +2758,7 @@ Löytääksesi '*', '?', '[', ']' tunnus Language Variant - Language Variant + Kielivariantti diff --git a/locale/fr_FR.ts b/locale/fr_FR.ts index 39cc9de40..1a194bed3 100644 --- a/locale/fr_FR.ts +++ b/locale/fr_FR.ts @@ -2757,7 +2757,7 @@ Pour rechercher les symboles '*', '?', '[', ' Language Variant - Language Variant + Variante de langue diff --git a/locale/hi_IN.ts b/locale/hi_IN.ts index 0c40eebd9..e1e10f3c8 100644 --- a/locale/hi_IN.ts +++ b/locale/hi_IN.ts @@ -2757,7 +2757,7 @@ Pour rechercher les symboles '*', '?', '[', ' Language Variant - Language Variant + भाषा संस्करण diff --git a/locale/it_IT.ts b/locale/it_IT.ts index 218c9591e..101092ad2 100644 --- a/locale/it_IT.ts +++ b/locale/it_IT.ts @@ -2759,7 +2759,7 @@ Per utilizzare nelle ricerche i caratteri '*', '?', '[& Language Variant - Language Variant + Variante linguistica diff --git a/locale/ja_JP.ts b/locale/ja_JP.ts index 6db51a7be..110e99665 100644 --- a/locale/ja_JP.ts +++ b/locale/ja_JP.ts @@ -2758,7 +2758,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + 言語のバリエーション diff --git a/locale/ko_KR.ts b/locale/ko_KR.ts index 5e668068b..32a6dc230 100644 --- a/locale/ko_KR.ts +++ b/locale/ko_KR.ts @@ -2758,7 +2758,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + 언어 변형 diff --git a/locale/lt_LT.ts b/locale/lt_LT.ts index 2340cdc28..7be6ecc25 100644 --- a/locale/lt_LT.ts +++ b/locale/lt_LT.ts @@ -2758,7 +2758,7 @@ Norėdami rasti „*“, „?“, „[“, „]“ simbolius, atitinkamai įvesk Language Variant - Language Variant + Kalbos variantas diff --git a/locale/mk_MK.ts b/locale/mk_MK.ts index 9e3683689..469bd80e4 100644 --- a/locale/mk_MK.ts +++ b/locale/mk_MK.ts @@ -2759,7 +2759,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + Јазична варијанта diff --git a/locale/nl_NL.ts b/locale/nl_NL.ts index 82b6a9138..1ab4d4282 100644 --- a/locale/nl_NL.ts +++ b/locale/nl_NL.ts @@ -2758,7 +2758,7 @@ Om '*'te vinden, '?', '[', ']' symbolen Language Variant - Language Variant + Taalvariant diff --git a/locale/pl_PL.ts b/locale/pl_PL.ts index a7203f68f..8fc8904fb 100644 --- a/locale/pl_PL.ts +++ b/locale/pl_PL.ts @@ -2758,7 +2758,7 @@ Aby odnaleźć symbole „*”, „?”, „[” i „]”, należy użyć odpow Language Variant - Language Variant + Wariant językowy diff --git a/locale/pt_BR.ts b/locale/pt_BR.ts index a5604267f..bcc9c60e2 100644 --- a/locale/pt_BR.ts +++ b/locale/pt_BR.ts @@ -2758,7 +2758,7 @@ Para encontrar os símbolos '*', '?', '[', '] Language Variant - Language Variant + Variante de idioma diff --git a/locale/pt_PT.ts b/locale/pt_PT.ts index 4431e477c..700eac67c 100644 --- a/locale/pt_PT.ts +++ b/locale/pt_PT.ts @@ -2758,7 +2758,7 @@ Para encontrar '*', '?', '[', ']' símbo Language Variant - Language Variant + Variante de idioma diff --git a/locale/qu_PE.ts b/locale/qu_PE.ts index 86fbc5e9b..c65d8a54e 100644 --- a/locale/qu_PE.ts +++ b/locale/qu_PE.ts @@ -2758,7 +2758,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + Lengua Variante diff --git a/locale/ru_RU.ts b/locale/ru_RU.ts index b329d0ec3..9276e2cd8 100644 --- a/locale/ru_RU.ts +++ b/locale/ru_RU.ts @@ -2760,7 +2760,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + Языковой вариант diff --git a/locale/sk_SK.ts b/locale/sk_SK.ts index ebf9ce1af..94952fe28 100644 --- a/locale/sk_SK.ts +++ b/locale/sk_SK.ts @@ -2759,7 +2759,7 @@ Pre vyhľadanie znakov '*', '?', '[', ']&apos Language Variant - Language Variant + Jazykový variant diff --git a/locale/sq_AL.ts b/locale/sq_AL.ts index 3c0800bd8..73962a04a 100644 --- a/locale/sq_AL.ts +++ b/locale/sq_AL.ts @@ -2757,7 +2757,7 @@ Për të gjetur '*', '?', '[', ']' simbo Language Variant - Language Variant + Varianti i gjuhës diff --git a/locale/sr_SP.ts b/locale/sr_SP.ts index 7134e5cc0..bf170f0e5 100644 --- a/locale/sr_SP.ts +++ b/locale/sr_SP.ts @@ -2759,7 +2759,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + Лангуаге Вариант diff --git a/locale/sv_SE.ts b/locale/sv_SE.ts index 818118fb4..a5c887914 100644 --- a/locale/sv_SE.ts +++ b/locale/sv_SE.ts @@ -2758,7 +2758,7 @@ För att hitta '*', '?', '[', ']' symbol Language Variant - Language Variant + Språkvariant diff --git a/locale/tg_TJ.ts b/locale/tg_TJ.ts index a9a6d49e0..34497feb2 100644 --- a/locale/tg_TJ.ts +++ b/locale/tg_TJ.ts @@ -2758,7 +2758,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + Варианти забон diff --git a/locale/tk_TM.ts b/locale/tk_TM.ts index 9f65aa1b4..8948e9a80 100644 --- a/locale/tk_TM.ts +++ b/locale/tk_TM.ts @@ -2757,7 +2757,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + Dil görnüşi diff --git a/locale/tr_TR.ts b/locale/tr_TR.ts index 441a8a5f8..7fc009e61 100644 --- a/locale/tr_TR.ts +++ b/locale/tr_TR.ts @@ -2758,7 +2758,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + Dil Varyantı diff --git a/locale/uk_UA.ts b/locale/uk_UA.ts index 0ec73d1b3..5c42e58b7 100644 --- a/locale/uk_UA.ts +++ b/locale/uk_UA.ts @@ -2759,7 +2759,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + Варіант мови diff --git a/locale/vi_VN.ts b/locale/vi_VN.ts index 5143a2784..0774d9ce1 100644 --- a/locale/vi_VN.ts +++ b/locale/vi_VN.ts @@ -2757,7 +2757,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + Biến thể ngôn ngữ diff --git a/locale/zh_TW.ts b/locale/zh_TW.ts index 5a8096e94..34a5ff3bd 100644 --- a/locale/zh_TW.ts +++ b/locale/zh_TW.ts @@ -2760,7 +2760,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + 語言變體 From 956af1374fd1a7b845e9069d212e8f1aff7dc505 Mon Sep 17 00:00:00 2001 From: xiaoyifang <105986+xiaoyifang@users.noreply.github.com> Date: Sun, 28 Jan 2024 10:38:49 +0800 Subject: [PATCH 12/67] New Crowdin updates (#1388) * New translations German, Switzerland from Crowdin * New translations German, Switzerland from Crowdin --- locale/de_CH.ts | 8 ++++---- locale/zh_CN.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/locale/de_CH.ts b/locale/de_CH.ts index 38c065f1a..45130c7e3 100644 --- a/locale/de_CH.ts +++ b/locale/de_CH.ts @@ -91,7 +91,7 @@ <h3 align="center">Welcome to <b>GoldenDict</b>!</h3><p>To start working with the program, first visit <em>Edit | Dictionaries</em> to add some directory paths where to search for the dictionary files, set up various Wikipedia sites or other sources, adjust dictionary order or create dictionary groups.<p>And then you're ready to look up your words! You can do that in this window by using a pane to the left, or you can <a href="https://xiaoyifang.github.io/goldendict-ng/ui_popup/">look up words from other active applications</a>. <p>To customize program, check out the available preferences at <em>Edit | Preferences</em>. All settings there have tooltips, be sure to read them if you are in doubt about anything.<p>Should you need further help, have any questions, suggestions or just wonder what the others think, you are welcome at the program's <a href="https://github.com/xiaoyifang/goldendict/discussions">forum</a>.<p>Check program's <a href="https://github.com/xiaoyifang/goldendict">website</a> for the updates. <p>(c) 2008-2013 Konstantin Isakov. Licensed under GPLv3 or later. - <h3 align="center">Welcome to <b>GoldenDict</b>!</h3><p>To start working with the program, first visit <em>Edit | Dictionaries</em> to add some directory paths where to search for the dictionary files, set up various Wikipedia sites or other sources, adjust dictionary order or create dictionary groups.<p>And then you're ready to look up your words! You can do that in this window by using a pane to the left, or you can <a href="https://xiaoyifang.github.io/goldendict-ng/ui_popup/">look up words from other active applications</a>. <p>To customize program, check out the available preferences at <em>Edit | Preferences</em>. All settings there have tooltips, be sure to read them if you are in doubt about anything.<p>Should you need further help, have any questions, suggestions or just wonder what the others think, you are welcome at the program's <a href="https://github.com/xiaoyifang/goldendict/discussions">forum</a>.<p>Check program's <a href="https://github.com/xiaoyifang/goldendict">website</a> for the updates. <p>(c) 2008-2013 Konstantin Isakov. Licensed under GPLv3 or later. + <h3 align="center">Willkommen bei <b>GoldenDict</b>!</h3><p>Um mit der Arbeit mit dem Programm zu beginnen, besuchen Sie zunächst <em>Bearbeiten | Wörterbücher</em>, um einige Verzeichnispfade hinzuzufügen, in denen Sie nach Wörterbuchdateien suchen, verschiedene Wikipedia-Sites oder andere Quellen einrichten, die Wörterbuchreihenfolge anpassen oder Wörterbuchgruppen erstellen können.<p>Und dann sind Sie bereit, Ihre Wörter nachzuschlagen! Sie können dies in diesem Fenster tun, indem Sie einen Bereich auf der linken Seite verwenden, oder Sie können <a href="https://xiaoyifang.github.io/goldendict-ng/ui_popup/">Wörter aus anderen aktiven Anwendungen nachschlagen</a>. <p>Um das Programm anzupassen, sehen Sie sich die verfügbaren Einstellungen unter <em>Bearbeiten | an Einstellungen</em>. Alle Einstellungen dort verfügen über Tooltips. Lesen Sie diese unbedingt durch, wenn Sie Zweifel haben.<p>Sollten Sie weitere Hilfe benötigen, Fragen oder Vorschläge haben oder sich einfach nur fragen, was die anderen denken, sind Sie im Programm willkommen <a href="https://github.com/xiaoyifang/goldendict/discussions">Forum</a>.<p>Suchen Sie auf der <a href="https://github.com/xiaoyifang/goldendict">Website</a> des Programms nach Aktualisierungen. <p>(c) 2008-2013 Konstantin Isakov. Lizenziert unter GPLv3 oder höher. @@ -274,7 +274,7 @@ Sound files (*.wav *.opus *.ogg *.oga *.mp3 *.mp4 *.aac *.flac *.mid *.wv *.ape *.spx);;All files (*.*) - Audio Dateien (*.wav *.ogg *.oga *.mp3 *.mp4 *.aac *.flac *.mid *.wv *.ape);;All files (*.*) + Audio Dateien (*.wav *.opus *.ogg *.oga *.mp3 *.mp4 *.aac *.flac *.mid *.wv *.ape *.spx);;Alle Dateien (*.*) Failed to play sound file: %1 @@ -3532,7 +3532,7 @@ Stardict, Babylon und GLS Wörterbüchern wünschen. Lingoes - Lingos + Lingoes Lingoes-Blue @@ -3972,7 +3972,7 @@ Standardisiert als ISO 3602. Any external programs. A string %GDWORD% will be replaced with the query word. A string %GDSEARCH% will be replaced with the text in the search bar. If both of the parameters are not provided, the headword will be fed into standard input. - Für externe Programme. Der String %GDWORD% wird mit dem Suchwort ersetzt. Falls eine solche Zeichenkette nicht vorhanden ist, wird das Wort an die Standardeingabe geschickt.. + Für externe Programme. Der String %GDWORD% wird mit dem Suchwort ersetzt. Der String %GDSEARCH% wird mit dem Text im Suchfeld ersetzt. Falls keine der beiden Zeichenketten vorhanden sind, wird das Hauptwort an die Standardeingabe geschickt. Lingua Libre diff --git a/locale/zh_CN.ts b/locale/zh_CN.ts index 7d3f9d656..03bf5477e 100644 --- a/locale/zh_CN.ts +++ b/locale/zh_CN.ts @@ -2757,7 +2757,7 @@ To find '*', '?', '[', ']' symbols use & Language Variant - Language Variant + 语言变体 From d15425acf2d95889fe6d3a2fb0665357c703531f Mon Sep 17 00:00:00 2001 From: xiaoyifang <105986+xiaoyifang@users.noreply.github.com> Date: Tue, 30 Jan 2024 09:40:46 +0800 Subject: [PATCH 13/67] New Crowdin updates (#1390) * New translations German, Switzerland from Crowdin * New translations German, Switzerland from Crowdin --- locale/de_CH.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locale/de_CH.ts b/locale/de_CH.ts index 45130c7e3..a78251de9 100644 --- a/locale/de_CH.ts +++ b/locale/de_CH.ts @@ -3740,11 +3740,11 @@ Stardict, Babylon und GLS Wörterbüchern wünschen. Change the group of popup. - Wechsle Gruppen im Popup. + Wechsle Gruppen im PopUp. Toggle scan popup. - Wechsle Scan Popup. + Wechsle ScanPopUp. Print version and diagnosis info. @@ -4264,7 +4264,7 @@ Fügen Sie entsprechende Wörterbücher am besten am Ende der passenden Gruppe e Rate: - Preis: + Tempo: From 85333df5c28ec563de96b1a9784eca686941f929 Mon Sep 17 00:00:00 2001 From: YiFang Xiao Date: Thu, 1 Feb 2024 13:20:13 +0800 Subject: [PATCH 14/67] opt: allow add directory in portable mode --- src/dict/sources.cc | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/src/dict/sources.cc b/src/dict/sources.cc index 10ffd2faa..949f466f5 100644 --- a/src/dict/sources.cc +++ b/src/dict/sources.cc @@ -131,32 +131,6 @@ Sources::Sources( QWidget * parent, Config::Class const & cfg ): ui.tabWidget->addTab( textToSpeechSource, QIcon( ":/icons/text2speech.svg" ), tr( "Text to Speech" ) ); } #endif - - if ( Config::isPortableVersion() ) { - // Paths - - ui.paths->setEnabled( false ); - ui.addPath->setEnabled( false ); - ui.removePath->setEnabled( false ); - - // Sound dirs - - { - QStandardItemModel * model = new QStandardItemModel( this ); - model->setHorizontalHeaderLabels( QStringList() << " " ); - model->invisibleRootItem()->appendRow( new QStandardItem( tr( "(not available in portable version)" ) ) ); - ui.soundDirs->setModel( model ); - ui.soundDirs->setEnabled( false ); - - ui.addSoundDir->setEnabled( false ); - ui.removeSoundDir->setEnabled( false ); - } - - // Morpho - - ui.hunspellPath->setEnabled( false ); - ui.changeHunspellPath->setEnabled( false ); - } } void Sources::fitPathsColumns() @@ -1137,6 +1111,13 @@ Qt::ItemFlags PathsModel::flags( QModelIndex const & index ) const { Qt::ItemFlags result = QAbstractItemModel::flags( index ); + if ( Config::isPortableVersion() ) { + if ( index.isValid() && index.row() == 0 ) { + result &= ~Qt::ItemIsSelectable; + result &= ~Qt::ItemIsEnabled; + } + } + if ( index.isValid() && index.column() == 1 ) result |= Qt::ItemIsUserCheckable; From a7e07ea51ce154b6892b199c9e2a94360d00e7b2 Mon Sep 17 00:00:00 2001 From: Canvis-Me <55122738+Canvis-Me@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:04:35 +0800 Subject: [PATCH 15/67] Fix cmake complains no --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bb368f03..6cfe2d521 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ find_package(Qt6 REQUIRED COMPONENTS Core5Compat LinguistTools Multimedia + PrintSupport WebEngineWidgets Widgets Svg @@ -125,6 +126,7 @@ target_link_libraries(${GOLDENDICT} PRIVATE Qt6::Concurrent Qt6::Core5Compat Qt6::Multimedia + Qt6::PrintSupport Qt6::WebEngineWidgets Qt6::Widgets Qt6::Svg From 1bb81c8fc1496a6b31b8b315a4507c6b50fdf500 Mon Sep 17 00:00:00 2001 From: YiFang Xiao Date: Fri, 16 Feb 2024 09:10:07 +0800 Subject: [PATCH 16/67] upgrade to qt6.6.2 --- .github/workflows/AutoTag.yml | 2 +- .github/workflows/macos-arm-homebrew.yml | 4 ++-- .github/workflows/macos-homebrew-breakpad.yml | 2 +- .github/workflows/macos-homebrew.yml | 4 ++-- .github/workflows/ubuntu-6.2.yml | 4 ++-- .github/workflows/ubuntu.yml | 2 +- .github/workflows/windows-6.x.yml | 4 ++-- .github/workflows/windows.yml | 2 +- CMakeLists.txt | 2 +- goldendict.pro | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/AutoTag.yml b/.github/workflows/AutoTag.yml index 55fa525ea..02f147a59 100644 --- a/.github/workflows/AutoTag.yml +++ b/.github/workflows/AutoTag.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest env: - version: 24.01.27 + version: 24.02.16 version-suffix: alpha prerelease: true diff --git a/.github/workflows/macos-arm-homebrew.yml b/.github/workflows/macos-arm-homebrew.yml index 24e054bcf..19335297e 100644 --- a/.github/workflows/macos-arm-homebrew.yml +++ b/.github/workflows/macos-arm-homebrew.yml @@ -22,11 +22,11 @@ jobs: strategy: matrix: os: [flyci-macos-large-latest-m2] - qt_ver: [ 6.6.1 ] + qt_ver: [ 6.6.2 ] qt_arch: [clang_64] env: targetName: GoldenDict - version: 24.01.27 + version: 24.02.16 version-suffix: alpha prerelease: true steps: diff --git a/.github/workflows/macos-homebrew-breakpad.yml b/.github/workflows/macos-homebrew-breakpad.yml index 565ade5e5..576ff7010 100644 --- a/.github/workflows/macos-homebrew-breakpad.yml +++ b/.github/workflows/macos-homebrew-breakpad.yml @@ -26,7 +26,7 @@ jobs: qt_arch: [clang_64] env: targetName: GoldenDict - version: 24.01.27 + version: 24.02.16 version-suffix: alpha prerelease: true steps: diff --git a/.github/workflows/macos-homebrew.yml b/.github/workflows/macos-homebrew.yml index edf1a6e23..e61f8ff4e 100644 --- a/.github/workflows/macos-homebrew.yml +++ b/.github/workflows/macos-homebrew.yml @@ -22,11 +22,11 @@ jobs: strategy: matrix: os: [macos-12,macos-13] - qt_ver: [ 6.6.1 ] + qt_ver: [ 6.6.2 ] qt_arch: [clang_64] env: targetName: GoldenDict - version: 24.01.27 + version: 24.02.16 version-suffix: alpha prerelease: true steps: diff --git a/.github/workflows/ubuntu-6.2.yml b/.github/workflows/ubuntu-6.2.yml index 9e60925c8..b7b3fd8a6 100644 --- a/.github/workflows/ubuntu-6.2.yml +++ b/.github/workflows/ubuntu-6.2.yml @@ -21,10 +21,10 @@ jobs: strategy: matrix: os: [ubuntu-20.04] - qt_ver: [ 6.6.1 ] + qt_ver: [ 6.6.2 ] qt_arch: [gcc_64] env: - version: 24.01.27 + version: 24.02.16 version-suffix: alpha prerelease: true steps: diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index f78922757..5221bf16f 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -25,7 +25,7 @@ jobs: qt_ver: [5.15.2] qt_arch: [gcc_64] env: - version: 24.01.27 + version: 24.02.16 version-suffix: alpha prerelease: true steps: diff --git a/.github/workflows/windows-6.x.yml b/.github/workflows/windows-6.x.yml index a786ef11f..4462e3aa7 100644 --- a/.github/workflows/windows-6.x.yml +++ b/.github/workflows/windows-6.x.yml @@ -27,11 +27,11 @@ jobs: strategy: matrix: os: [windows-2019] - qt_ver: [ 6.6.1 ] + qt_ver: [ 6.6.2 ] qt_arch: [win64_msvc2019_64] env: targetName: GoldenDict.exe - version: 24.01.27 + version: 24.02.16 version-suffix: alpha prerelease: true steps: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index b9853a86e..6cb4118e2 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -27,7 +27,7 @@ jobs: qt_arch: [win64_msvc2019_64] env: targetName: GoldenDict.exe - version: 24.01.27 + version: 24.02.16 version-suffix: alpha prerelease: true # 步骤 diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cfe2d521..f1fc886b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ option(USE_ALTERNATIVE_NAME "Force the name goldendict-ng " OFF) include(FeatureSummary) project(goldendict-ng - VERSION 24.01.27 + VERSION 24.02.16 LANGUAGES CXX C) if (NOT USE_ALTERNATIVE_NAME) diff --git a/goldendict.pro b/goldendict.pro index c5d0140f3..c45fc1572 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -1,6 +1,6 @@ TEMPLATE = app TARGET = goldendict -VERSION = 24.01.27 +VERSION = 24.02.16 # Generate version file. We do this here and in a build rule described later. # The build rule is required since qmake isn't run each time the project is @@ -133,7 +133,7 @@ win32 { win32-msvc* { # VS does not recognize 22.number.alpha,cause errors during compilation under MSVC++ - VERSION = 24.01.27 + VERSION = 24.02.16 DEFINES += __WIN32 _CRT_SECURE_NO_WARNINGS contains(QMAKE_TARGET.arch, x86_64) { DEFINES += NOMINMAX __WIN64 From b3778da093447afe537eecc591451f3f522907b3 Mon Sep 17 00:00:00 2001 From: Xu Jiyong Date: Fri, 16 Feb 2024 09:11:16 +0800 Subject: [PATCH 17/67] Typo fix for Mediawiki language variant (#1398) * opt: add the language variant option for wikipedia dictionaries * fix:default value for 'lang_' parameter * [autofix.ci] apply automated fixes * fix: Should have been 'https' for the wikipedia queries. * fix:typo traditional -> Traditional --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/config.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.cc b/src/config.cc index 47c7dbd9b..c544f2931 100644 --- a/src/config.cc +++ b/src/config.cc @@ -355,13 +355,13 @@ MediaWikis makeDefaultMediaWikis( bool enable ) mw.push_back( MediaWiki( "5d45232075d06e002dea72fe3e137da1", "Greek Wiktionary", "https://el.wiktionary.org/w", false, "" ) ); mw.push_back( MediaWiki( "c015d60c4949ad75b5b7069c2ff6dc2c", - "traditional Chinese Wikipedia", + "Traditional Chinese Wikipedia", "https://zh.wikipedia.org/w", false, "", "zh-hant" ) ); mw.push_back( MediaWiki( "d50828ad6e115bc9d3421b6821543108", - "traditional Chinese Wiktionary", + "Traditional Chinese Wiktionary", "https://zh.wiktionary.org/w", false, "", From 68c95001005fd77c95936589bbc2487ff3086019 Mon Sep 17 00:00:00 2001 From: YiFang Xiao Date: Fri, 16 Feb 2024 10:25:55 +0800 Subject: [PATCH 18/67] modify release note --- .github/workflows/macos-arm-homebrew.yml | 2 +- .github/workflows/macos-homebrew-breakpad.yml | 2 +- .github/workflows/macos-homebrew.yml | 2 +- .github/workflows/ubuntu-6.2.yml | 2 +- .github/workflows/ubuntu.yml | 2 +- .github/workflows/windows-6.x.yml | 2 +- .github/workflows/windows.yml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/macos-arm-homebrew.yml b/.github/workflows/macos-arm-homebrew.yml index 19335297e..04778e527 100644 --- a/.github/workflows/macos-arm-homebrew.yml +++ b/.github/workflows/macos-arm-homebrew.yml @@ -211,7 +211,7 @@ jobs: #### Build Details - AppImage: Ubuntu-20.04 + Flatpak macOS: macOS-12 and macOS-13 Windows: Visual studio 2019 based on: ${{github.ref_name}} diff --git a/.github/workflows/macos-homebrew-breakpad.yml b/.github/workflows/macos-homebrew-breakpad.yml index 576ff7010..3465328c8 100644 --- a/.github/workflows/macos-homebrew-breakpad.yml +++ b/.github/workflows/macos-homebrew-breakpad.yml @@ -212,7 +212,7 @@ jobs: #### Build Details - AppImage: Ubuntu-20.04 + Flatpak macOS: macOS-12 and macOS-13 Windows: Visual studio 2019 based on: ${{github.ref_name}} diff --git a/.github/workflows/macos-homebrew.yml b/.github/workflows/macos-homebrew.yml index e61f8ff4e..3e0686e03 100644 --- a/.github/workflows/macos-homebrew.yml +++ b/.github/workflows/macos-homebrew.yml @@ -210,7 +210,7 @@ jobs: #### Build Details - AppImage: Ubuntu-20.04 + Flatpak macOS: macOS-12 and macOS-13 Windows: Visual studio 2019 based on: ${{github.ref_name}} diff --git a/.github/workflows/ubuntu-6.2.yml b/.github/workflows/ubuntu-6.2.yml index b7b3fd8a6..20f540fe4 100644 --- a/.github/workflows/ubuntu-6.2.yml +++ b/.github/workflows/ubuntu-6.2.yml @@ -194,7 +194,7 @@ jobs: #### Build Details - AppImage: Ubuntu-20.04 + Flatpak macOS: macOS-12 and macOS-13 Windows: Visual studio 2019 based on: ${{github.ref_name}} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 5221bf16f..ec8a4169c 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -181,7 +181,7 @@ jobs: #### Build Details - AppImage: Ubuntu-20.04 + Flatpak macOS: macOS-12 and macOS-13 Windows: Visual studio 2019 based on: ${{github.ref_name}} diff --git a/.github/workflows/windows-6.x.yml b/.github/workflows/windows-6.x.yml index 4462e3aa7..8c06fd3c5 100644 --- a/.github/workflows/windows-6.x.yml +++ b/.github/workflows/windows-6.x.yml @@ -259,7 +259,7 @@ jobs: #### Build Details - AppImage: Ubuntu-20.04 + Flatpak macOS: macOS-12 and macOS-13 Windows: Visual studio 2019 based on: ${{github.ref_name}} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 6cb4118e2..a2d3419f4 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -229,7 +229,7 @@ jobs: #### Build Details - AppImage: Ubuntu-20.04 + Flatpak macOS: macOS-12 and macOS-13 Windows: Visual studio 2019 based on: ${{github.ref_name}} From 4d58fd83dbad85f83d4763550d31ee99986e7f59 Mon Sep 17 00:00:00 2001 From: YiFang Xiao Date: Sat, 17 Feb 2024 21:55:20 +0800 Subject: [PATCH 19/67] fix: lost dictionary folder after restart --- src/main.cc | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main.cc b/src/main.cc index 8b74a4981..b3443a95b 100644 --- a/src/main.cc +++ b/src/main.cc @@ -508,15 +508,6 @@ int main( int argc, char ** argv ) qInstallMessageHandler( gdMessageHandler ); } - if ( Config::isPortableVersion() ) { - // For portable version, hardcode some settings - - cfg.paths.clear(); - cfg.paths.push_back( Config::Path( Config::getPortableVersionDictionaryDir(), true ) ); - cfg.soundDirs.clear(); - cfg.hunspell.dictionariesPath = Config::getPortableVersionMorphoDir(); - } - // Reload translations for user selected locale is nesessary QTranslator qtTranslator; QTranslator translator; From 0c023d32f6ebe3be48b4cd5d1d4b52c55cb6c1b3 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 19 Feb 2024 13:12:16 +0000 Subject: [PATCH 20/67] Announce Goldendict-ng for guix --- website/docs/install.md | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/install.md b/website/docs/install.md index 3e2ace9c3..631539615 100644 --- a/website/docs/install.md +++ b/website/docs/install.md @@ -24,6 +24,7 @@ If Qt's version is not changed, you can also download a single `goldendict.exe` Download on Flathub * See the right side for available packages in various Linux distros. +* In Gnu Guix, goldendict-ng is available at the [ajattix repository](https://codeberg.org/hashirama/ajattix) * In Debian 12 and Ubuntu 23.04, `goldendict-webengine` is available (For later versions it is `goldendict-ng`). * Pre-built binary is also available from [archlinuxcn's repo](https://github.com/archlinuxcn/repo/tree/master/archlinuxcn/goldendict-ng-git). * [Gentoo package from PG_Overlay](https://gitlab.com/Perfect_Gentleman/PG_Overlay/-/blob/master/app-text/goldendict/goldendict-9999-r6.ebuild) From 32391bda43175d346d00a43158ffee903679ca80 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Tue, 27 Feb 2024 05:07:00 -0500 Subject: [PATCH 21/67] fix: reduce darkreader mode white flash further by using QWebEnginePage::setBackgroundColor() (for Qt6.6.3+) (#1406) * fix: reduce darkreader mode white flash further by using QWebEnginePage::setBackgroundColor() (for Qt6.6.3+) In previous Qt6.x versions, this function only set background once and become invalidated if reload a page. * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/ui/articleview.cc | 16 ++++++++++++++++ src/ui/articleview.hh | 3 +++ src/ui/mainwindow.cc | 2 +- src/ui/scanpopup.cc | 2 ++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/ui/articleview.cc b/src/ui/articleview.cc index 09de2c87d..d8e7fb37a 100644 --- a/src/ui/articleview.cc +++ b/src/ui/articleview.cc @@ -155,6 +155,8 @@ ArticleView::ArticleView( QWidget * parent, webview->setUp( const_cast< Config::Class * >( &cfg ) ); + syncBackgroundColorWithCfgDarkReader(); + goBackAction.setShortcut( QKeySequence( "Alt+Left" ) ); webview->addAction( &goBackAction ); connect( &goBackAction, &QAction::triggered, this, &ArticleView::back ); @@ -1360,6 +1362,20 @@ void ArticleView::setDelayedHighlightText( QString const & text ) delayedHighlightText = text; } +void ArticleView::syncBackgroundColorWithCfgDarkReader() const +{ +// Only works Qt6.6.3+ https://bugreports.qt.io/browse/QTBUG-112013 +#if QT_VERSION >= QT_VERSION_CHECK( 6, 6, 3 ) + if ( cfg.preferences.darkReaderMode ) { + webview->page()->setBackgroundColor( Qt::black ); + } + else { + webview->page()->setBackgroundColor( Qt::white ); + } +#endif +} + + void ArticleView::back() { // Don't allow navigating back to page 0, which is usually the initial diff --git a/src/ui/articleview.hh b/src/ui/articleview.hh index 94d3fe525..7544be7e0 100644 --- a/src/ui/articleview.hh +++ b/src/ui/articleview.hh @@ -172,6 +172,9 @@ public: void setDelayedHighlightText( QString const & text ); + /// \brief Set background as black if darkreader mode is enabled. + void syncBackgroundColorWithCfgDarkReader() const; + private: // widgets ArticleWebView * webview; diff --git a/src/ui/mainwindow.cc b/src/ui/mainwindow.cc index 03f6bf434..a5b8480bb 100644 --- a/src/ui/mainwindow.cc +++ b/src/ui/mainwindow.cc @@ -2270,7 +2270,7 @@ void MainWindow::editPreferences() ArticleView & view = dynamic_cast< ArticleView & >( *( ui.tabWidget->widget( x ) ) ); view.setSelectionBySingleClick( p.selectWordBySingleClick ); - + view.syncBackgroundColorWithCfgDarkReader(); if ( needReload ) { view.reload(); } diff --git a/src/ui/scanpopup.cc b/src/ui/scanpopup.cc index 026fc2b11..ef8c415f1 100644 --- a/src/ui/scanpopup.cc +++ b/src/ui/scanpopup.cc @@ -291,6 +291,8 @@ void ScanPopup::refresh() updateDictionaryBar(); + definition->syncBackgroundColorWithCfgDarkReader(); + connect( ui.groupList, &GroupComboBox::currentIndexChanged, this, &ScanPopup::currentGroupChanged ); #ifdef HAVE_X11 selectionDelayTimer.setInterval( cfg.preferences.selectionChangeDelayTimer ); From 171a5b78f8302a7a29b3da87a907f1e79704c6ef Mon Sep 17 00:00:00 2001 From: YiFang Xiao Date: Tue, 27 Feb 2024 18:15:37 +0800 Subject: [PATCH 22/67] opt: css feature content-visibility only available in modern style --- src/stylesheets/article-style-st-classic.css | 2 -- src/stylesheets/article-style-st-modern.css | 3 +++ src/stylesheets/article-style.css | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/stylesheets/article-style-st-classic.css b/src/stylesheets/article-style-st-classic.css index 94f835e0d..a861aaf89 100644 --- a/src/stylesheets/article-style-st-classic.css +++ b/src/stylesheets/article-style-st-classic.css @@ -67,8 +67,6 @@ pre { background: #fefdeb; /*fix for invalid blg*/ font-style: normal; - content-visibility: auto; - contain-intrinsic-height: auto 600px; } /* CSS trick to prevent the floating elements to overflow diff --git a/src/stylesheets/article-style-st-modern.css b/src/stylesheets/article-style-st-modern.css index e8761e36a..718273615 100644 --- a/src/stylesheets/article-style-st-modern.css +++ b/src/stylesheets/article-style-st-modern.css @@ -25,6 +25,9 @@ a:hover { background: white; padding-left: 2em; padding-right: 2em; + + content-visibility: auto; + contain-intrinsic-height: auto 600px; } .gdactivearticle { diff --git a/src/stylesheets/article-style.css b/src/stylesheets/article-style.css index fc2740bc0..c566dd7b9 100644 --- a/src/stylesheets/article-style.css +++ b/src/stylesheets/article-style.css @@ -99,8 +99,6 @@ pre { margin-bottom: 8px; /*fix for invalid blg*/ font-style: normal; - content-visibility: auto; - contain-intrinsic-height: auto 600px; } .empty-space { From 8f77f2a716c21b46214ed36fb4f655d2a274c16e Mon Sep 17 00:00:00 2001 From: David Date: Wed, 28 Feb 2024 15:18:57 -0900 Subject: [PATCH 23/67] Fix two typos in desktop file --- redist/io.github.xiaoyifang.goldendict_ng.desktop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/redist/io.github.xiaoyifang.goldendict_ng.desktop b/redist/io.github.xiaoyifang.goldendict_ng.desktop index 126dcd8d3..3ad7fdb30 100755 --- a/redist/io.github.xiaoyifang.goldendict_ng.desktop +++ b/redist/io.github.xiaoyifang.goldendict_ng.desktop @@ -7,8 +7,8 @@ GenericName=Multiformat Dictionary GenericName[zh_CN]=多格式字典 Comment=A feature-rich dictionary lookup program Comment[zh_CN]=多功能字典查询软件 -Keywords=dict;dictioanry; -Keywords[zh_CN]=dict;dictioanry;字典; +Keywords=dict;dictionary; +Keywords[zh_CN]=dict;dictionary;字典; Icon=goldendict Exec=goldendict %u MimeType=x-scheme-handler/goldendict;x-scheme-handler/dict; From dd956909409a95950118d6fd06b5beaef89abe17 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Mon, 18 Mar 2024 05:45:06 -0400 Subject: [PATCH 24/67] clean: simplify tabbar double click detect via qt's built-in methods (#1419) * clean: simplify tabbar double click detect via qt's built-in methods * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/ui/maintabwidget.cc | 18 ------------------ src/ui/maintabwidget.hh | 6 ------ src/ui/mainwindow.cc | 6 +++++- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/ui/maintabwidget.cc b/src/ui/maintabwidget.cc index fffae353d..f9c94f360 100644 --- a/src/ui/maintabwidget.cc +++ b/src/ui/maintabwidget.cc @@ -42,26 +42,8 @@ void MainTabWidget::updateTabBarVisibility() tabBar()->setVisible( !hideSingleTab || tabBar()->count() > 1 ); } -/* -void MainTabWidget::mouseDoubleClickEvent ( QMouseEvent * event ) -{ - (void) event; - emit doubleClicked(); -} -*/ - bool MainTabWidget::eventFilter( QObject * obj, QEvent * ev ) { - // mouseDoubleClickEvent don't called under Ubuntu - if ( ev->type() == QEvent::MouseButtonDblClick ) { - QMouseEvent * mev = static_cast< QMouseEvent * >( ev ); - if ( mev->y() >= tabBar()->rect().y() && mev->y() <= tabBar()->rect().y() + tabBar()->rect().height() - && tabBar()->tabAt( mev->pos() ) == -1 ) { - emit doubleClicked(); - return true; - } - } - if ( obj == tabBar() && ev->type() == QEvent::MouseButtonPress ) { QMouseEvent * mev = static_cast< QMouseEvent * >( ev ); if ( mev->button() == Qt::MiddleButton ) { diff --git a/src/ui/maintabwidget.hh b/src/ui/maintabwidget.hh index dcbc24045..ceabc5077 100644 --- a/src/ui/maintabwidget.hh +++ b/src/ui/maintabwidget.hh @@ -23,12 +23,6 @@ public: } void setHideSingleTab( bool hide ); -signals: - void doubleClicked(); - -protected: - // virtual void mouseDoubleClickEvent ( QMouseEvent * event ); - private: virtual void tabInserted( int index ); virtual void tabRemoved( int index ); diff --git a/src/ui/mainwindow.cc b/src/ui/mainwindow.cc index a5b8480bb..dc1fc3b91 100644 --- a/src/ui/mainwindow.cc +++ b/src/ui/mainwindow.cc @@ -636,7 +636,11 @@ MainWindow::MainWindow( Config::Class & cfg_ ): connect( &addTab, &QAbstractButton::clicked, this, &MainWindow::addNewTab ); - connect( ui.tabWidget, &MainTabWidget::doubleClicked, this, &MainWindow::addNewTab ); + connect( ui.tabWidget, &MainTabWidget::tabBarDoubleClicked, [ this ]( const int index ) { + if ( -1 == index ) { // empty space at tabbar clicked. + this->addNewTab(); + } + } ); connect( ui.tabWidget, &QTabWidget::tabCloseRequested, this, &MainWindow::tabCloseRequested ); From d546cafda446a73206b5a862c614fc96c732bd7c Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Tue, 19 Mar 2024 03:18:21 -0400 Subject: [PATCH 25/67] fix: a crash when qrcx:// fails. The reply here might be nullptr. --- src/resourceschemehandler.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/resourceschemehandler.cc b/src/resourceschemehandler.cc index a4cd2ec6e..8aaad1b90 100644 --- a/src/resourceschemehandler.cc +++ b/src/resourceschemehandler.cc @@ -12,7 +12,12 @@ void ResourceSchemeHandler::requestStarted( QWebEngineUrlRequestJob * requestJob const QMimeType mineType = db.mimeTypeForUrl( url ); const sptr< Dictionary::DataRequest > reply = this->mManager.getResource( url, content_type ); content_type = mineType.name(); - if ( reply->isFinished() ) { + + if ( reply == nullptr ) { + qDebug() << "Resource failed to load: " << url.toString(); + requestJob->fail( QWebEngineUrlRequestJob::RequestFailed ); + } + else if ( reply->isFinished() ) { replyJob( reply, requestJob, content_type ); } else From 97a3824e9fedd3ea1e71802a6e6bf4041d8e11d0 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Tue, 19 Mar 2024 22:40:44 -0400 Subject: [PATCH 26/67] fix: crash when clicking play audio insanely fast. --- src/ffmpegaudio.cc | 14 ++++++++++---- src/ffmpegaudio.hh | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/ffmpegaudio.cc b/src/ffmpegaudio.cc index 0a7e6d713..da4616588 100644 --- a/src/ffmpegaudio.cc +++ b/src/ffmpegaudio.cc @@ -52,11 +52,12 @@ AudioService::~AudioService() void AudioService::playMemory( const char * ptr, int size ) { emit cancelPlaying( false ); + if ( !thread.isNull() ) { + thread->wait(); + } QByteArray audioData( ptr, size ); - thread = std::make_shared< DecoderThread >( audioData, this ); - connect( this, &AudioService::cancelPlaying, thread.get(), [ this ]( bool waitFinished ) { - thread->cancel( waitFinished ); - } ); + thread.reset( new DecoderThread( audioData, this ) ); + connect( this, &AudioService::cancelPlaying, thread.get(), &DecoderThread::cancel ); thread->start(); } @@ -263,6 +264,11 @@ bool DecoderContext::openOutputDevice( QString & errorString ) } #endif + if ( audioOutput == nullptr ) { + errorString += QStringLiteral( "Failed to create audioOutput." ); + return false; + } + audioOutput->setAudioFormat( 44100, codecContext_->channels ); return true; } diff --git a/src/ffmpegaudio.hh b/src/ffmpegaudio.hh index 87482c27f..9e53b8550 100644 --- a/src/ffmpegaudio.hh +++ b/src/ffmpegaudio.hh @@ -29,7 +29,7 @@ class DecoderThread; class AudioService: public QObject { Q_OBJECT - std::shared_ptr< DecoderThread > thread; + QScopedPointer< DecoderThread > thread; public: static AudioService & instance(); From b9e14f806ca3b50b5f2ea66bd31f2c2f0e5f8577 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Tue, 19 Mar 2024 23:08:17 -0400 Subject: [PATCH 27/67] fix: CMake problems found by the openBSD package (#1422) * fix: CMake problems found by openBSD package * remove unused WITH_XAPIAN option * don't link Qt TTS if not requested * fix: address CMake problems found by openBSD package * remove unused WITH_XAPIAN option * don't link Qt TTS if not requested * Disable some code when TTS is not requested * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- CMakeLists.txt | 29 ++++++++++------------------- CMake_Unix.cmake | 13 +++---------- src/config.cc | 6 ++++-- src/config.hh | 4 ++++ src/dict/loaddictionaries.cc | 2 ++ src/dict/sources.cc | 4 ++++ src/dict/sources.hh | 8 ++++---- src/dict/voiceengines.cc | 25 ++++++++++++++----------- src/dict/voiceengines.hh | 13 ++++++------- src/main.cc | 2 ++ src/speechclient.cc | 12 ++++++++---- src/speechclient.hh | 18 +++++++++--------- src/texttospeechsource.cc | 11 +++++++---- src/texttospeechsource.hh | 17 +++++++++-------- src/ui/articleview.cc | 5 +++++ src/ui/editdictionaries.cc | 9 +++++++-- src/version.hh | 1 - 17 files changed, 98 insertions(+), 81 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f1fc886b1..d6ddc35cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,6 @@ cmake_minimum_required(VERSION 3.25) # ubuntu 23.04 Fedora 36 option(WITH_FFMPEG_PLAYER "Enable support for FFMPEG player" ON) option(WITH_EPWING_SUPPORT "Enable epwing support" ON) -option(WITH_XAPIAN "enable Xapian support" ON) option(WITH_ZIM "enable zim support" ON) option(WITH_TTS "enable QTexttoSpeech support" ON) @@ -36,18 +35,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) #### Qt -find_package(Qt6 REQUIRED COMPONENTS - Concurrent - Core5Compat - LinguistTools - Multimedia - PrintSupport - WebEngineWidgets - Widgets - Svg - Xml - TextToSpeech - ) +set(GD_QT_COMPONENTS Concurrent Core5Compat LinguistTools Multimedia PrintSupport WebEngineWidgets Widgets Svg Xml) + +if (WITH_TTS) + list(APPEND GD_QT_COMPONENTS TextToSpeech) +endif () + +find_package(Qt6 REQUIRED COMPONENTS ${GD_QT_COMPONENTS}) qt_standard_project_setup() # availiable after find_package(Qt6 .... Core set(CMAKE_AUTORCC ON) # not included in the qt_standard_project_setup @@ -130,9 +124,11 @@ target_link_libraries(${GOLDENDICT} PRIVATE Qt6::WebEngineWidgets Qt6::Widgets Qt6::Svg - Qt6::TextToSpeech ) +if (WITH_TTS) + target_link_libraries(${GOLDENDICT} PRIVATE Qt6::TextToSpeech) +endif () target_include_directories(${GOLDENDICT} PRIVATE ${PROJECT_SOURCE_DIR}/thirdparty/qtsingleapplication/src @@ -172,11 +168,6 @@ if (NOT WITH_EPWING_SUPPORT) target_compile_definitions(${GOLDENDICT} PUBLIC NO_EPWING_SUPPORT) endif () - -if (WITH_XAPIAN) - target_compile_definitions(${GOLDENDICT} PUBLIC USE_XAPIAN) -endif () - if (WITH_ZIM) target_compile_definitions(${GOLDENDICT} PUBLIC MAKE_ZIM_SUPPORT) endif () diff --git a/CMake_Unix.cmake b/CMake_Unix.cmake index b17804f75..db066f198 100644 --- a/CMake_Unix.cmake +++ b/CMake_Unix.cmake @@ -28,13 +28,10 @@ endif () ##### Finding packages from package manager - find_package(PkgConfig REQUIRED) -# Provided by Cmake find_package(ZLIB REQUIRED) find_package(BZip2 REQUIRED) - # Consider all PkgConfig dependencies as one pkg_check_modules(PKGCONFIG_DEPS IMPORTED_TARGET hunspell @@ -43,7 +40,8 @@ pkg_check_modules(PKGCONFIG_DEPS IMPORTED_TARGET vorbis # .ogg vorbisfile liblzma - ) + xapian-core +) target_link_libraries(${GOLDENDICT} PRIVATE PkgConfig::PKGCONFIG_DEPS @@ -69,15 +67,10 @@ if (WITH_FFMPEG_PLAYER) libavformat libavutil libswresample - ) + ) target_link_libraries(${GOLDENDICT} PRIVATE PkgConfig::FFMPEG) endif () -if (WITH_XAPIAN) - find_package(Xapian REQUIRED) # https://github.com/xapian/xapian/tree/master/xapian-core/cmake - target_link_libraries(${GOLDENDICT} PRIVATE ${XAPIAN_LIBRARIES}) -endif () - if (WITH_EPWING_SUPPORT) find_library(EB_LIBRARY eb REQUIRED) target_link_libraries(${GOLDENDICT} PRIVATE ${EB_LIBRARY}) diff --git a/src/config.cc b/src/config.cc index c544f2931..1e25c3f87 100644 --- a/src/config.cc +++ b/src/config.cc @@ -845,7 +845,7 @@ Class load() // Upgrading c.dictServers = makeDefaultDictServers(); } - +#ifndef NO_TTS_SUPPORT QDomNode ves = root.namedItem( "voiceEngines" ); if ( !ves.isNull() ) { @@ -872,6 +872,7 @@ Class load() c.voiceEngines.push_back( v ); } } +#endif c.mutedDictionaries = loadMutedDictionaries( root.namedItem( "mutedDictionaries" ) ); c.popupMutedDictionaries = loadMutedDictionaries( root.namedItem( "popupMutedDictionaries" ) ); @@ -1664,7 +1665,7 @@ void save( Class const & c ) p.setAttributeNode( icon ); } } - +#ifndef NO_TTS_SUPPORT { QDomNode ves = dd.createElement( "voiceEngines" ); root.appendChild( ves ); @@ -1706,6 +1707,7 @@ void save( Class const & c ) v.setAttributeNode( rate ); } } +#endif { QDomElement muted = dd.createElement( "mutedDictionaries" ); diff --git a/src/config.hh b/src/config.hh index ed057c57a..0a552c48e 100644 --- a/src/config.hh +++ b/src/config.hh @@ -779,6 +779,7 @@ struct Program typedef QVector< Program > Programs; +#ifndef NO_TTS_SUPPORT struct VoiceEngine { bool enabled; @@ -823,6 +824,7 @@ struct VoiceEngine }; typedef QVector< VoiceEngine > VoiceEngines; +#endif struct HeadwordsDialog { @@ -856,7 +858,9 @@ struct Class Lingua lingua; Forvo forvo; Programs programs; +#ifndef NO_TTS_SUPPORT VoiceEngines voiceEngines; +#endif unsigned lastMainGroupId; // Last used group in main window unsigned lastPopupGroupId; // Last used group in popup window diff --git a/src/dict/loaddictionaries.cc b/src/dict/loaddictionaries.cc index 5a358fc10..17d561e93 100644 --- a/src/dict/loaddictionaries.cc +++ b/src/dict/loaddictionaries.cc @@ -269,7 +269,9 @@ void loadDictionaries( QWidget * parent, addDicts( Forvo::makeDictionaries( loadDicts, cfg.forvo, dictNetMgr ) ); addDicts( Lingua::makeDictionaries( loadDicts, cfg.lingua, dictNetMgr ) ); addDicts( Programs::makeDictionaries( cfg.programs ) ); +#ifndef NO_TTS_SUPPORT addDicts( VoiceEngines::makeDictionaries( cfg.voiceEngines ) ); +#endif addDicts( DictServer::makeDictionaries( cfg.dictServers ) ); diff --git a/src/dict/sources.cc b/src/dict/sources.cc index 949f466f5..d42baf5b2 100644 --- a/src/dict/sources.cc +++ b/src/dict/sources.cc @@ -16,7 +16,9 @@ Sources::Sources( QWidget * parent, Config::Class const & cfg ): #ifdef MAKE_CHINESE_CONVERSION_SUPPORT chineseConversion( new ChineseConversion( this, cfg.transliteration.chinese ) ), #endif +#ifndef NO_TTS_SUPPORT textToSpeechSource( nullptr ), +#endif itemDelegate( new QItemDelegate( this ) ), itemEditorFactory( new QItemEditorFactory() ), mediawikisModel( this, cfg.mediawikis ), @@ -317,12 +319,14 @@ void Sources::on_removeProgram_clicked() programsModel.removeProgram( current.row() ); } +#ifndef NO_TTS_SUPPORT Config::VoiceEngines Sources::getVoiceEngines() const { if ( !textToSpeechSource ) return Config::VoiceEngines(); return textToSpeechSource->getVoiceEnginesModel().getCurrentVoiceEngines(); } +#endif Config::Hunspell Sources::getHunspell() const { diff --git a/src/dict/sources.hh b/src/dict/sources.hh index 38fbd13ed..0754f7aac 100644 --- a/src/dict/sources.hh +++ b/src/dict/sources.hh @@ -296,9 +296,9 @@ public: { return programsModel.getCurrentPrograms(); } - +#ifndef NO_TTS_SUPPORT Config::VoiceEngines getVoiceEngines() const; - +#endif Config::Hunspell getHunspell() const; Config::Transliteration getTransliteration() const; @@ -318,9 +318,9 @@ private: #ifdef MAKE_CHINESE_CONVERSION_SUPPORT ChineseConversion * chineseConversion; #endif - +#ifndef NO_TTS_SUPPORT TextToSpeechSource * textToSpeechSource; - +#endif QItemDelegate * itemDelegate; QScopedPointer< QItemEditorFactory > itemEditorFactory; diff --git a/src/dict/voiceengines.cc b/src/dict/voiceengines.cc index 3530c52b4..9aabda436 100644 --- a/src/dict/voiceengines.cc +++ b/src/dict/voiceengines.cc @@ -1,20 +1,21 @@ /* This file is (c) 2013 Timon Wong * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ +#ifndef NO_TTS_SUPPORT -#include "voiceengines.hh" -#include "audiolink.hh" -#include "htmlescape.hh" -#include "utf8.hh" -#include "wstring_qt.hh" + #include "voiceengines.hh" + #include "audiolink.hh" + #include "htmlescape.hh" + #include "utf8.hh" + #include "wstring_qt.hh" -#include -#include + #include + #include -#include -#include -#include + #include + #include + #include -#include "utils.hh" + #include "utils.hh" namespace VoiceEngines { @@ -137,3 +138,5 @@ vector< sptr< Dictionary::Class > > makeDictionaries( Config::VoiceEngines const } } // namespace VoiceEngines + +#endif \ No newline at end of file diff --git a/src/dict/voiceengines.hh b/src/dict/voiceengines.hh index ae54a1262..b535a1a5e 100644 --- a/src/dict/voiceengines.hh +++ b/src/dict/voiceengines.hh @@ -1,14 +1,13 @@ /* This file is (c) 2013 Timon Wong * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ +#pragma once +#ifndef NO_TTS_SUPPORT -#ifndef __VOICEENGINES_HH_INCLUDED__ -#define __VOICEENGINES_HH_INCLUDED__ + #include "dictionary.hh" + #include "config.hh" + #include "wstring.hh" -#include "dictionary.hh" -#include "config.hh" -#include "wstring.hh" - -#include + #include namespace VoiceEngines { diff --git a/src/main.cc b/src/main.cc index b3443a95b..27eb29526 100644 --- a/src/main.cc +++ b/src/main.cc @@ -488,7 +488,9 @@ int main( int argc, char ** argv ) if ( gdcl.notts ) { cfg.notts = true; +#ifndef NO_TTS_SUPPORT cfg.voiceEngines.clear(); +#endif } cfg.resetState = gdcl.resetState; diff --git a/src/speechclient.cc b/src/speechclient.cc index 92ae9ccde..a98a9cf89 100644 --- a/src/speechclient.cc +++ b/src/speechclient.cc @@ -1,8 +1,10 @@ -#include "speechclient.hh" +#ifndef NO_TTS_SUPPORT -#include -#include -#include + #include "speechclient.hh" + + #include + #include + #include SpeechClient::SpeechClient( Config::VoiceEngine const & e, QObject * parent ): QObject( parent ), internalData( new InternalData( e ) ) @@ -67,3 +69,5 @@ bool SpeechClient::tell( QString const & text ) const { return tell( text, internalData->engine.volume, internalData->engine.rate ); } + +#endif \ No newline at end of file diff --git a/src/speechclient.hh b/src/speechclient.hh index 826adef7e..51ae3cb50 100644 --- a/src/speechclient.hh +++ b/src/speechclient.hh @@ -1,12 +1,12 @@ -#ifndef __SPEECHCLIENT_HH_INCLUDED__ -#define __SPEECHCLIENT_HH_INCLUDED__ +#pragma once +#ifndef NO_TTS_SUPPORT -#include -#include "config.hh" -#include -#include -#include -#include + #include + #include "config.hh" + #include + #include + #include + #include class SpeechClient: public QObject { @@ -81,4 +81,4 @@ private: QSharedPointer< InternalData > internalData; }; -#endif // __SPEECHCLIENT_HH_INCLUDED__ +#endif diff --git a/src/texttospeechsource.cc b/src/texttospeechsource.cc index e23f4a67c..bb1c0773d 100644 --- a/src/texttospeechsource.cc +++ b/src/texttospeechsource.cc @@ -1,10 +1,11 @@ /* This file is (c) 2013 Timon Wong * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ +#ifndef NO_TTS_SUPPORT -#include "texttospeechsource.hh" -#include -#include -#include + #include "texttospeechsource.hh" + #include + #include + #include TextToSpeechSource::TextToSpeechSource( QWidget * parent, Config::VoiceEngines voiceEngines ): QWidget( parent ), @@ -431,3 +432,5 @@ void VoiceEngineItemDelegate::setModelData( QWidget * uncastedEditor, model->setData( engineIdIndex, editor->engineId() ); model->setData( engineNameIndex, editor->engineName() ); } + +#endif \ No newline at end of file diff --git a/src/texttospeechsource.hh b/src/texttospeechsource.hh index d3be3594d..c178d86f7 100644 --- a/src/texttospeechsource.hh +++ b/src/texttospeechsource.hh @@ -1,15 +1,16 @@ /* This file is (c) 2013 Timon Wong * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ -#ifndef __TEXTTOSPEECHSOURCE_HH_INCLUDED__ -#define __TEXTTOSPEECHSOURCE_HH_INCLUDED__ +#pragma once -#include "ui_texttospeechsource.h" -#include "config.hh" -#include "speechclient.hh" +#ifndef NO_TTS_SUPPORT -#include -#include + #include "ui_texttospeechsource.h" + #include "config.hh" + #include "speechclient.hh" + + #include + #include /// A model to be projected into the text to speech view, according to Qt's MVC model class VoiceEnginesModel: public QAbstractItemModel @@ -116,4 +117,4 @@ private: void adjustSliders(); }; -#endif // __TEXTTOSPEECHSOURCE_HH_INCLUDED__ +#endif diff --git a/src/ui/articleview.cc b/src/ui/articleview.cc index d8e7fb37a..e343a111f 100644 --- a/src/ui/articleview.cc +++ b/src/ui/articleview.cc @@ -1182,6 +1182,7 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref, QString const & QMessageBox::critical( this, "GoldenDict", tr( "The referenced audio program doesn't exist." ) ); } else if ( url.scheme() == "gdtts" ) { +#ifndef NO_TTS_SUPPORT // Text to speech QString md5Id = Utils::Url::queryItemValue( url, "engine" ); QString text( url.path().mid( 1 ) ); @@ -1197,7 +1198,11 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref, QString const & break; } } +#else + qDebug() << "gdtts:// is not supported due to missing TTS support"; +#endif } + else if ( Utils::isExternalLink( url ) ) { // Use the system handler for the conventional external links QDesktopServices::openUrl( url ); diff --git a/src/ui/editdictionaries.cc b/src/ui/editdictionaries.cc index 3d3147396..b2e8a4203 100644 --- a/src/ui/editdictionaries.cc +++ b/src/ui/editdictionaries.cc @@ -171,7 +171,11 @@ bool EditDictionaries::isSourcesChanged() const || sources.getHunspell() != cfg.hunspell || sources.getTransliteration() != cfg.transliteration || sources.getLingua() != cfg.lingua || sources.getForvo() != cfg.forvo || sources.getMediaWikis() != cfg.mediawikis || sources.getWebSites() != cfg.webSites || sources.getDictServers() != cfg.dictServers - || sources.getPrograms() != cfg.programs || sources.getVoiceEngines() != cfg.voiceEngines; + || sources.getPrograms() != cfg.programs +#ifndef NO_TTS_SUPPORT + || sources.getVoiceEngines() != cfg.voiceEngines +#endif + ; } void EditDictionaries::acceptChangedSources( bool rebuildGroups ) @@ -192,8 +196,9 @@ void EditDictionaries::acceptChangedSources( bool rebuildGroups ) cfg.webSites = sources.getWebSites(); cfg.dictServers = sources.getDictServers(); cfg.programs = sources.getPrograms(); +#ifndef NO_TTS_SUPPORT cfg.voiceEngines = sources.getVoiceEngines(); - +#endif ui.tabs->setUpdatesEnabled( false ); // Those hold pointers to dictionaries, we need to free them. groupInstances.clear(); diff --git a/src/version.hh b/src/version.hh index acd764287..7eba944b7 100644 --- a/src/version.hh +++ b/src/version.hh @@ -5,7 +5,6 @@ namespace Version { const QLatin1String flags = QLatin1String( - "USE_XAPIAN" #ifdef MAKE_ZIM_SUPPORT " MAKE_ZIM_SUPPORT" #endif From 483381414fa92dd34995a36a327327fcd7e666f1 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Tue, 19 Mar 2024 23:11:35 -0400 Subject: [PATCH 28/67] clean: remove macOS unused code related to gestures (#1421) * clean: remove macOS unused code related to gestures We prefer native gestures and disabled gesture.cc long time ago, those are unused * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/gestures.cc | 16 +++++++++------- src/gestures.hh | 17 +++++++++-------- src/ui/articleview.cc | 8 +++++--- src/ui/mainwindow.cc | 6 ++++-- src/ui/scanpopup.cc | 5 ++++- 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/gestures.cc b/src/gestures.cc index a302063c1..ddbf1191a 100644 --- a/src/gestures.cc +++ b/src/gestures.cc @@ -1,12 +1,12 @@ /* This file is (c) 2014 Abs62 * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ - -#include -#include -#include -#include -#include "ui/articleview.hh" -#include "gestures.hh" +#ifndef __APPLE__ + #include + #include + #include + #include + #include "ui/articleview.hh" + #include "gestures.hh" namespace Gestures { @@ -351,3 +351,5 @@ void unregisterRecognizers() } } // namespace Gestures + +#endif \ No newline at end of file diff --git a/src/gestures.hh b/src/gestures.hh index 0f5b0b1a5..9f0c75a76 100644 --- a/src/gestures.hh +++ b/src/gestures.hh @@ -1,11 +1,11 @@ -#ifndef __GESTURES_HH_INCLUDED__ -#define __GESTURES_HH_INCLUDED__ +#pragma once -#include -#include -#include -#include -#include +#ifndef __APPLE__ + #include + #include + #include + #include + #include namespace Gestures { @@ -120,4 +120,5 @@ bool handleGestureEvent( QObject * obj, QEvent * event, GestureResult & result, } // namespace Gestures -#endif // __GESTURES_HH_INCLUDED__ + +#endif \ No newline at end of file diff --git a/src/ui/articleview.cc b/src/ui/articleview.cc index e343a111f..7ddfc62d1 100644 --- a/src/ui/articleview.cc +++ b/src/ui/articleview.cc @@ -250,10 +250,10 @@ ArticleView::ArticleView( QWidget * parent, webview->setHtml( QString::fromStdString( html ) ); expandOptionalParts = cfg.preferences.alwaysExpandOptionalParts; - +#ifndef Q_OS_MACOS webview->grabGesture( Gestures::GDPinchGestureType ); webview->grabGesture( Gestures::GDSwipeGestureType ); - +#endif // Variable name for store current selection range rangeVarName = QString( "sr_%1" ).arg( QString::number( (quint64)this, 16 ) ); @@ -300,8 +300,10 @@ ArticleView::~ArticleView() cleanupTemp(); audioPlayer->stop(); //channel->deregisterObject(this); +#ifndef Q_OS_MACOS webview->ungrabGesture( Gestures::GDPinchGestureType ); webview->ungrabGesture( Gestures::GDSwipeGestureType ); +#endif } void ArticleView::showDefinition( QString const & word, @@ -724,7 +726,6 @@ bool ArticleView::eventFilter( QObject * obj, QEvent * ev ) return handled; } -#endif if ( ev->type() == QEvent::MouseMove ) { if ( Gestures::isFewTouchPointsPresented() ) { @@ -732,6 +733,7 @@ bool ArticleView::eventFilter( QObject * obj, QEvent * ev ) return true; } } +#endif if ( handleF3( obj, ev ) ) { return true; diff --git a/src/ui/mainwindow.cc b/src/ui/mainwindow.cc index dc1fc3b91..2cd1cdc37 100644 --- a/src/ui/mainwindow.cc +++ b/src/ui/mainwindow.cc @@ -905,9 +905,10 @@ MainWindow::MainWindow( Config::Class & cfg_ ): wasMaximized = isMaximized(); history.setSaveInterval( cfg.preferences.historyStoreInterval ); - +#ifndef Q_OS_MACOS ui.centralWidget->grabGesture( Gestures::GDPinchGestureType ); ui.centralWidget->grabGesture( Gestures::GDSwipeGestureType ); +#endif if ( layoutDirection() == Qt::RightToLeft ) { // Adjust button icons for Right-To-Left layout @@ -1158,9 +1159,10 @@ MainWindow::~MainWindow() closeHeadwordsDialog(); ftsIndexing.stopIndexing(); - +#ifndef Q_OS_MACOS ui.centralWidget->ungrabGesture( Gestures::GDPinchGestureType ); ui.centralWidget->ungrabGesture( Gestures::GDSwipeGestureType ); +#endif // Gestures::unregisterRecognizers(); // Close all tabs -- they should be destroyed before network managers diff --git a/src/ui/scanpopup.cc b/src/ui/scanpopup.cc index ef8c415f1..5b4aa34d5 100644 --- a/src/ui/scanpopup.cc +++ b/src/ui/scanpopup.cc @@ -251,8 +251,10 @@ ScanPopup::ScanPopup( QWidget * parent, ui.goBackButton->setEnabled( false ); ui.goForwardButton->setEnabled( false ); +#ifndef Q_OS_MACOS grabGesture( Gestures::GDPinchGestureType ); grabGesture( Gestures::GDSwipeGestureType ); +#endif #ifdef HAVE_X11 scanFlag = new ScanFlag( this ); @@ -303,9 +305,10 @@ void ScanPopup::refresh() ScanPopup::~ScanPopup() { saveConfigData(); - +#ifndef Q_OS_MACOS ungrabGesture( Gestures::GDPinchGestureType ); ungrabGesture( Gestures::GDSwipeGestureType ); +#endif } void ScanPopup::saveConfigData() const From a3d7faeffecbe410b22f77f51ea4a96cc6075f92 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Wed, 20 Mar 2024 00:07:03 -0400 Subject: [PATCH 29/67] add developer_name to metadata.xml as required by flathub --- redist/io.github.xiaoyifang.goldendict_ng.metainfo.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/redist/io.github.xiaoyifang.goldendict_ng.metainfo.xml b/redist/io.github.xiaoyifang.goldendict_ng.metainfo.xml index 1e377798f..8bcdda415 100644 --- a/redist/io.github.xiaoyifang.goldendict_ng.metainfo.xml +++ b/redist/io.github.xiaoyifang.goldendict_ng.metainfo.xml @@ -5,6 +5,7 @@ CC0-1.0 GPL-3.0-or-later GoldenDict-ng + The GoldenDict-ng Community Advanced dictionary lookup program Education From 0b61888bc7d9ebd167deec48486abe45e5e0fa1f Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Wed, 20 Mar 2024 00:43:28 -0400 Subject: [PATCH 30/67] fix: Open Image in System Viewer doesn't work on macOS --- src/ui/articleview.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/articleview.cc b/src/ui/articleview.cc index d8e7fb37a..d7d37fbc0 100644 --- a/src/ui/articleview.cc +++ b/src/ui/articleview.cc @@ -1792,11 +1792,11 @@ void ArticleView::contextMenuRequested( QPoint const & pos ) if ( !handler->isEmpty() ) { connect( handler, &ResourceToSaveHandler::done, this, [ fileName ]() { - QDesktopServices::openUrl( fileName ); + QDesktopServices::openUrl( QUrl::fromLocalFile( fileName ) ); } ); } else { - QDesktopServices::openUrl( fileName ); + QDesktopServices::openUrl( QUrl::fromLocalFile( fileName ) ); } } } From 68ed2da4c2c22f45f09d26296c4c9fabb4b98294 Mon Sep 17 00:00:00 2001 From: xiaoyifang <105986+xiaoyifang@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:45:20 +0800 Subject: [PATCH 31/67] fix: macos build (#1428) * Update macos-homebrew.yml * Update macos-arm-homebrew.yml --- .github/workflows/macos-arm-homebrew.yml | 2 +- .github/workflows/macos-homebrew.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/macos-arm-homebrew.yml b/.github/workflows/macos-arm-homebrew.yml index 04778e527..fd76c2bd6 100644 --- a/.github/workflows/macos-arm-homebrew.yml +++ b/.github/workflows/macos-arm-homebrew.yml @@ -47,7 +47,7 @@ jobs: brew install autoconf brew install libtool brew install opencc - brew install automake git lame libass libtool shtool texi2html theora wget xvid nasm + brew install automake libtool brew install libiconv brew install lzo bzip2 diff --git a/.github/workflows/macos-homebrew.yml b/.github/workflows/macos-homebrew.yml index 3e0686e03..ab55bb707 100644 --- a/.github/workflows/macos-homebrew.yml +++ b/.github/workflows/macos-homebrew.yml @@ -47,7 +47,7 @@ jobs: brew install autoconf brew install libtool brew install opencc - brew install automake git lame libass libtool shtool texi2html theora wget xvid nasm + brew install automake libtool brew install libiconv brew install lzo bzip2 From e8f3a94541068f2464bc5eaaa38d95858c6b3148 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Wed, 20 Mar 2024 09:44:17 -0400 Subject: [PATCH 32/67] fix: soundDir index not updating when sounds modified (#1427) --- src/dict/sounddir.cc | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/dict/sounddir.cc b/src/dict/sounddir.cc index f0f3b9147..a61852cd8 100644 --- a/src/dict/sounddir.cc +++ b/src/dict/sounddir.cc @@ -17,6 +17,7 @@ #include #include #include +#include namespace SoundDir { @@ -438,7 +439,24 @@ vector< sptr< Dictionary::Class > > makeDictionaries( Config::SoundDirs const & string indexFile = indicesDir + dictId; - if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) ) { + // Check if the soundDir and its subdirs' modification date changed, that means the user modified the sound files inside + + bool soundDirModified = false; + { + QDateTime indexFileModifyTime = QFileInfo( QString::fromStdString( indexFile ) ).lastModified(); + QDirIterator it( dir.path(), + QDir::AllDirs | QDir::NoDotAndDotDot | QDir::NoSymLinks, + QDirIterator::Subdirectories ); + while ( it.hasNext() ) { + it.next(); + if ( it.fileInfo().lastModified() > indexFileModifyTime ) { + soundDirModified = true; + break; + } + } + } + + if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) || soundDirModified ) { // Building the index qDebug() << "Sounds: Building the index for directory: " << soundDir.path; From be22cb22b60b023394fa22a327a0fe9d75f2513a Mon Sep 17 00:00:00 2001 From: xiaoyifang <105986+xiaoyifang@users.noreply.github.com> Date: Wed, 20 Mar 2024 22:07:58 +0800 Subject: [PATCH 33/67] fix: in portable version,the default `content` dir lost recursive attribute. (#1430) * fix: allow edit the content path * fix: portable version ,add default item * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/config.cc | 5 +++++ src/dict/sources.cc | 7 ------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/config.cc b/src/config.cc index 1e25c3f87..d81627808 100644 --- a/src/config.cc +++ b/src/config.cc @@ -632,6 +632,11 @@ Class load() Path( nl.item( x ).toElement().text(), nl.item( x ).toElement().attribute( "recursive" ) == "1" ) ); } + if ( Config::isPortableVersion() && c.paths.empty() ) { + // For portable version, hardcode some settings + c.paths.push_back( Config::Path( Config::getPortableVersionDictionaryDir(), true ) ); + } + QDomNode soundDirs = root.namedItem( "sounddirs" ); if ( !soundDirs.isNull() ) { diff --git a/src/dict/sources.cc b/src/dict/sources.cc index d42baf5b2..87a1d7bb3 100644 --- a/src/dict/sources.cc +++ b/src/dict/sources.cc @@ -1115,13 +1115,6 @@ Qt::ItemFlags PathsModel::flags( QModelIndex const & index ) const { Qt::ItemFlags result = QAbstractItemModel::flags( index ); - if ( Config::isPortableVersion() ) { - if ( index.isValid() && index.row() == 0 ) { - result &= ~Qt::ItemIsSelectable; - result &= ~Qt::ItemIsEnabled; - } - } - if ( index.isValid() && index.column() == 1 ) result |= Qt::ItemIsUserCheckable; From 57a5c526208d04b63381e5ff8d4319c2bb179a1f Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Thu, 21 Mar 2024 19:09:05 -0400 Subject: [PATCH 34/67] clean: remove unnecessary passing of openSearchAction to ArticleView's ctor --- src/ui/articleview.cc | 5 ----- src/ui/articleview.hh | 8 +++----- src/ui/mainwindow.cc | 3 ++- src/ui/scanpopup.cc | 6 +++--- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/ui/articleview.cc b/src/ui/articleview.cc index fce90d767..ed6940b52 100644 --- a/src/ui/articleview.cc +++ b/src/ui/articleview.cc @@ -94,7 +94,6 @@ ArticleView::ArticleView( QWidget * parent, Instances::Groups const & groups_, bool popupView_, Config::Class const & cfg_, - QAction & openSearchAction_, QLineEdit const * translateLine_, QAction * dictionaryBarToggled_, GroupComboBox const * groupComboBox_ ): @@ -113,7 +112,6 @@ ArticleView::ArticleView( QWidget * parent, selectCurrentArticleAction( this ), copyAsTextAction( this ), inspectAction( this ), - openSearchAction( openSearchAction_ ), searchIsOpened( false ), dictionaryBarToggled( dictionaryBarToggled_ ), groupComboBox( groupComboBox_ ), @@ -201,9 +199,6 @@ ArticleView::ArticleView( QWidget * parent, webview->addAction( &articleDownAction ); connect( &articleDownAction, &QAction::triggered, this, &ArticleView::moveOneArticleDown ); - webview->addAction( &openSearchAction ); - connect( &openSearchAction, &QAction::triggered, this, &ArticleView::openSearch ); - selectCurrentArticleAction.setShortcut( QKeySequence( "Ctrl+Shift+A" ) ); selectCurrentArticleAction.setText( tr( "Select Current Article" ) ); webview->addAction( &selectCurrentArticleAction ); diff --git a/src/ui/articleview.hh b/src/ui/articleview.hh index 7544be7e0..a29d05df9 100644 --- a/src/ui/articleview.hh +++ b/src/ui/articleview.hh @@ -49,7 +49,6 @@ class ArticleView: public QWidget QAction pasteAction, articleUpAction, articleDownAction, goBackAction, goForwardAction, selectCurrentArticleAction, copyAsTextAction, inspectAction; - QAction & openSearchAction; bool searchIsOpened; bool expandOptionalParts; QString rangeVarName; @@ -105,7 +104,6 @@ public: Instances::Groups const &, bool popupView, Config::Class const & cfg, - QAction & openSearchAction_, QLineEdit const * translateLine, QAction * dictionaryBarToggled = nullptr, GroupComboBox const * groupComboBox = nullptr ); @@ -319,6 +317,9 @@ signals: public slots: + /// Opens the search (Ctrl+F) + void openSearch(); + void on_searchPrevious_clicked(); void on_searchNext_clicked(); @@ -361,9 +362,6 @@ private slots: /// Nagivates to the next article relative to the active one. void moveOneArticleDown(); - /// Opens the search area - void openSearch(); - void on_searchText_textEdited(); void on_searchText_returnPressed(); void on_searchCloseButton_clicked(); diff --git a/src/ui/mainwindow.cc b/src/ui/mainwindow.cc index 2cd1cdc37..c77092f03 100644 --- a/src/ui/mainwindow.cc +++ b/src/ui/mainwindow.cc @@ -1783,7 +1783,6 @@ ArticleView * MainWindow::createNewTab( bool switchToIt, QString const & name ) groupInstances, false, cfg, - *ui.searchInPageAction, translateLine, dictionaryBar.toggleViewAction(), groupList ); @@ -1823,6 +1822,8 @@ ArticleView * MainWindow::createNewTab( bool switchToIt, QString const & name ) connect( view, &ArticleView::zoomOut, this, &MainWindow::zoomout ); connect( view, &ArticleView::saveBookmarkSignal, this, &MainWindow::addBookmarkToFavorite ); + connect( ui.searchInPageAction, &QAction::triggered, view, &ArticleView::openSearch ); + view->setSelectionBySingleClick( cfg.preferences.selectWordBySingleClick ); int index = cfg.preferences.newTabsOpenAfterCurrentOne ? ui.tabWidget->currentIndex() + 1 : ui.tabWidget->count(); diff --git a/src/ui/scanpopup.cc b/src/ui/scanpopup.cc index 5b4aa34d5..c52eae249 100644 --- a/src/ui/scanpopup.cc +++ b/src/ui/scanpopup.cc @@ -85,8 +85,6 @@ ScanPopup::ScanPopup( QWidget * parent, { ui.setupUi( this ); - openSearchAction.setShortcut( QKeySequence( "Ctrl+F" ) ); - if ( layoutDirection() == Qt::RightToLeft ) { // Adjust button icons for Right-To-Left layout ui.goBackButton->setIcon( QIcon( ":/icons/next.svg" ) ); @@ -104,7 +102,6 @@ ScanPopup::ScanPopup( QWidget * parent, groups, true, cfg, - openSearchAction, ui.translateBox->translateLine(), dictionaryBar.toggleViewAction() ); @@ -114,6 +111,9 @@ ScanPopup::ScanPopup( QWidget * parent, connect( definition, &ArticleView::sendWordToHistory, this, &ScanPopup::sendWordToHistory ); connect( definition, &ArticleView::typingEvent, this, &ScanPopup::typingEvent ); + openSearchAction.setShortcut( QKeySequence( "Ctrl+F" ) ); + connect( &openSearchAction, &QAction::triggered, definition, &ArticleView::openSearch ); + wordListDefaultFont = ui.translateBox->completerWidget()->font(); translateLineDefaultFont = ui.translateBox->font(); groupListDefaultFont = ui.groupList->font(); From 48fc8fceffdb673e4883a10b0eff9f4f414471c0 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Thu, 21 Mar 2024 20:45:29 -0400 Subject: [PATCH 35/67] clean: deprecations that won't break Qt5 compatibility --- src/audioplayerfactory.cc | 4 ++-- src/dict/sources.cc | 2 +- src/externalaudioplayer.cc | 10 +++++----- src/externalaudioplayer.hh | 17 ++++++++--------- src/headwordsmodel.cc | 2 +- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/audioplayerfactory.cc b/src/audioplayerfactory.cc index 05ff67c33..3e37ee369 100644 --- a/src/audioplayerfactory.cc +++ b/src/audioplayerfactory.cc @@ -65,9 +65,9 @@ void AudioPlayerFactory::reset() #endif } - QScopedPointer< ExternalAudioPlayer > externalPlayer( new ExternalAudioPlayer ); + std::unique_ptr< ExternalAudioPlayer > externalPlayer( new ExternalAudioPlayer ); setAudioPlaybackProgram( *externalPlayer ); - playerPtr.reset( externalPlayer.take() ); + playerPtr.reset( externalPlayer.release() ); } void AudioPlayerFactory::setAudioPlaybackProgram( ExternalAudioPlayer & externalPlayer ) diff --git a/src/dict/sources.cc b/src/dict/sources.cc index 87a1d7bb3..ced7b6e85 100644 --- a/src/dict/sources.cc +++ b/src/dict/sources.cc @@ -41,7 +41,7 @@ Sources::Sources( QWidget * parent, Config::Class const & cfg ): // anyone? QItemEditorCreatorBase * programTypeEditorCreator = new QStandardItemEditorCreator< ProgramTypeEditor >(); - itemEditorFactory->registerEditor( QVariant::Int, programTypeEditorCreator ); + itemEditorFactory->registerEditor( QMetaType::Int, programTypeEditorCreator ); itemDelegate->setItemEditorFactory( itemEditorFactory.get() ); diff --git a/src/externalaudioplayer.cc b/src/externalaudioplayer.cc index fef94c3b5..66725b841 100644 --- a/src/externalaudioplayer.cc +++ b/src/externalaudioplayer.cc @@ -16,7 +16,7 @@ ExternalAudioPlayer::~ExternalAudioPlayer() // Set viewer to null first and foremost to make sure that onViewerDestroyed() // doesn't attempt to start viewer or mess the smart pointer up. - stopAndDestroySynchronously( viewer.take() ); + stopAndDestroySynchronously( viewer.release() ); stopAndDestroySynchronously( exitingViewer ); } @@ -56,7 +56,7 @@ void ExternalAudioPlayer::stop() // 1) the process gets a chance to clean up and save its state; // 2) there is no event loop blocking and consequently no (short) UI freeze // while synchronously waiting for the external process to exit. - exitingViewer = viewer.take(); + exitingViewer = viewer.release(); } else // viewer is either not started or already stopped -> simply destroy it. viewer.reset(); @@ -72,14 +72,14 @@ void ExternalAudioPlayer::onViewerDestroyed( QObject * destroyedViewer ) emit error( errorMessage ); } } - else if ( viewer.data() == destroyedViewer ) - viewer.take(); // viewer finished and died -> release ownership. + else if ( viewer.get() == destroyedViewer ) + viewer.reset(nullptr); // viewer finished and died -> reset } QString ExternalAudioPlayer::startViewer() { Q_ASSERT( !exitingViewer && viewer ); - connect( viewer.data(), &QObject::destroyed, this, &ExternalAudioPlayer::onViewerDestroyed ); + connect( viewer.get(), &QObject::destroyed, this, &ExternalAudioPlayer::onViewerDestroyed ); try { viewer->start(); } diff --git a/src/externalaudioplayer.hh b/src/externalaudioplayer.hh index a85467626..b48fcb359 100644 --- a/src/externalaudioplayer.hh +++ b/src/externalaudioplayer.hh @@ -4,9 +4,8 @@ #ifndef EXTERNALAUDIOPLAYER_HH_INCLUDED #define EXTERNALAUDIOPLAYER_HH_INCLUDED -#include -#include #include "audioplayerinterface.hh" +#include class ExternalViewer; @@ -34,16 +33,16 @@ private: ///< the current viewer (if any) is not started yet ///< and waits for exitingViewer to be destroyed first. - struct ScopedPointerDeleteLater + struct QObjectDeleteLater { - static void cleanup( QObject * p ) - { - if ( p ) - p->deleteLater(); - } + void operator()( QObject * p ) + { + if ( p ) + p->deleteLater(); + } }; // deleteLater() is safer because viewer actively participates in the QEventLoop. - QScopedPointer< ExternalViewer, ScopedPointerDeleteLater > viewer; + std::unique_ptr< ExternalViewer, QObjectDeleteLater > viewer; }; #endif // EXTERNALAUDIOPLAYER_HH_INCLUDED diff --git a/src/headwordsmodel.cc b/src/headwordsmodel.cc index d5ea4499b..ee081e5e2 100644 --- a/src/headwordsmodel.cc +++ b/src/headwordsmodel.cc @@ -150,7 +150,7 @@ void HeadwordListModel::fetchMore( const QModelIndex & parent ) } QSet< QString > filtered; - for ( const auto & word : qAsConst( headword ) ) { + for ( const auto & word : std::as_const( headword ) ) { if ( !containWord( word ) ) filtered.insert( word ); } From 1724b4f32b701e4db86decb0e72a1ac2cffd9d52 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 01:50:46 +0000 Subject: [PATCH 36/67] [autofix.ci] apply automated fixes --- src/externalaudioplayer.cc | 2 +- src/externalaudioplayer.hh | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/externalaudioplayer.cc b/src/externalaudioplayer.cc index 66725b841..89268cc05 100644 --- a/src/externalaudioplayer.cc +++ b/src/externalaudioplayer.cc @@ -73,7 +73,7 @@ void ExternalAudioPlayer::onViewerDestroyed( QObject * destroyedViewer ) } } else if ( viewer.get() == destroyedViewer ) - viewer.reset(nullptr); // viewer finished and died -> reset + viewer.reset( nullptr ); // viewer finished and died -> reset } QString ExternalAudioPlayer::startViewer() diff --git a/src/externalaudioplayer.hh b/src/externalaudioplayer.hh index b48fcb359..02cb52140 100644 --- a/src/externalaudioplayer.hh +++ b/src/externalaudioplayer.hh @@ -35,11 +35,11 @@ private: struct QObjectDeleteLater { - void operator()( QObject * p ) - { - if ( p ) - p->deleteLater(); - } + void operator()( QObject * p ) + { + if ( p ) + p->deleteLater(); + } }; // deleteLater() is safer because viewer actively participates in the QEventLoop. std::unique_ptr< ExternalViewer, QObjectDeleteLater > viewer; From 8ad68d96dfeb5ed7c5f576fcc3824118e9977aa0 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Fri, 22 Mar 2024 01:00:08 -0400 Subject: [PATCH 37/67] fix: url handler and encoded ACE / Punycode / percent encoded URLs --- src/main.cc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main.cc b/src/main.cc index 27eb29526..8d573e69b 100644 --- a/src/main.cc +++ b/src/main.cc @@ -301,6 +301,24 @@ void processCommandLine( QCoreApplication * app, GDOptions * result ) result->word.chop( 1 ); } } + + // Handle cases where we get encoded URL + if ( result->word.startsWith( QStringLiteral( "xn--" ) ) ) { + // For `kde-open` or `gio` or others, URL are encoded into ACE or Punycode + #if QT_VERSION >= QT_VERSION_CHECK( 6, 3, 0 ) + result->word = QUrl::fromAce( result->word.toLatin1(), QUrl::IgnoreIDNWhitelist ); + #else + // Old Qt's fromAce only applies to whitelisted domains, so we add .com to bypass this restriction :) + // https://bugreports.qt.io/browse/QTBUG-29080 + result->word.append( QStringLiteral( ".com" ) ); + result->word = QUrl::fromAce( result->word.toLatin1() ); + result->word.chop( 4 ); + #endif + } + else if ( result->word.startsWith( QStringLiteral( "%" ) ) ) { + // For Firefox or other browsers where URL are percent encoded + result->word = QUrl::fromPercentEncoding( result->word.toLatin1() ); + } #endif } } From f8c0e8cd172c3e170e053e46d48cac73dfc11458 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Sat, 23 Mar 2024 00:29:51 -0400 Subject: [PATCH 38/67] clean: rename File::Class to what it is really used. --- src/btreeidx.cc | 6 +++--- src/btreeidx.hh | 6 +++--- src/chunkedstorage.cc | 4 ++-- src/chunkedstorage.hh | 8 ++++---- src/common/file.cc | 40 ++++++++++++++++++++-------------------- src/common/file.hh | 14 +++++--------- src/dict/aard.cc | 10 +++++----- src/dict/bgl.cc | 14 +++++++------- src/dict/dictdfiles.cc | 8 ++++---- src/dict/dsl.cc | 6 +++--- src/dict/epwing.cc | 6 +++--- src/dict/gls.cc | 6 +++--- src/dict/lsa.cc | 14 +++++++------- src/dict/mdx.cc | 6 +++--- src/dict/sdict.cc | 10 +++++----- src/dict/slob.cc | 6 +++--- src/dict/sounddir.cc | 8 ++++---- src/dict/stardict.cc | 14 +++++++------- src/dict/xdxf.cc | 6 +++--- src/dict/zim.cc | 6 +++--- src/dict/zipsounds.cc | 6 +++--- 21 files changed, 100 insertions(+), 104 deletions(-) diff --git a/src/btreeidx.cc b/src/btreeidx.cc index 22132dc3d..bc1d44470 100644 --- a/src/btreeidx.cc +++ b/src/btreeidx.cc @@ -50,7 +50,7 @@ string const & BtreeDictionary::ensureInitDone() return empty; } -void BtreeIndex::openIndex( IndexInfo const & indexInfo, File::Class & file, QMutex & mutex ) +void BtreeIndex::openIndex( IndexInfo const & indexInfo, File::Index & file, QMutex & mutex ) { indexNodeSize = indexInfo.btreeMaxElements; rootOffset = indexInfo.rootOffset; @@ -754,7 +754,7 @@ void BtreeIndex::antialias( wstring const & str, vector< WordArticleLink > & cha /// leaf nodes. static uint32_t buildBtreeNode( IndexedWords::const_iterator & nextIndex, size_t indexSize, - File::Class & file, + File::Index & file, size_t maxElements, uint32_t & lastLeafLinkOffset ) { @@ -984,7 +984,7 @@ void IndexedWords::addSingleWord( wstring const & index_word, uint32_t articleOf operator[]( Utf8::encode( folded ) ).emplace_back( Utf8::encode( word ), articleOffset ); } -IndexInfo buildIndex( IndexedWords const & indexedWords, File::Class & file ) +IndexInfo buildIndex( IndexedWords const & indexedWords, File::Index & file ) { size_t indexSize = indexedWords.size(); auto nextIndex = indexedWords.begin(); diff --git a/src/btreeidx.hh b/src/btreeidx.hh index 77c905460..07b4ce3f0 100644 --- a/src/btreeidx.hh +++ b/src/btreeidx.hh @@ -82,7 +82,7 @@ public: /// Opens the index. The file reference is saved to be used for /// subsequent lookups. /// The mutex is the one to be locked when working with the file. - void openIndex( IndexInfo const &, File::Class &, QMutex & ); + void openIndex( IndexInfo const &, File::Index &, QMutex & ); /// Finds articles that match the given string. A case-insensitive search /// is performed. @@ -140,7 +140,7 @@ protected: protected: QMutex * idxFileMutex; - File::Class * idxFile; + File::Index * idxFile; private: @@ -276,7 +276,7 @@ struct IndexedWords: public map< string, vector< WordArticleLink > > /// Builds the index, as a compressed btree. Returns IndexInfo. /// All the data is stored to the given file, beginning from its current /// position. -IndexInfo buildIndex( IndexedWords const &, File::Class & file ); +IndexInfo buildIndex( IndexedWords const &, File::Index & file ); } // namespace BtreeIndexing diff --git a/src/chunkedstorage.cc b/src/chunkedstorage.cc index 9a33e7a4a..c9fa93397 100644 --- a/src/chunkedstorage.cc +++ b/src/chunkedstorage.cc @@ -14,7 +14,7 @@ enum { ChunkMaxSize = 65536 // Can't be more since it would overflow the address }; -Writer::Writer( File::Class & f ): +Writer::Writer( File::Index & f ): file( f ), chunkStarted( false ), bufferUsed( 0 ) @@ -115,7 +115,7 @@ uint32_t Writer::finish() return offset; } -Reader::Reader( File::Class & f, uint32_t offset ): +Reader::Reader( File::Index & f, uint32_t offset ): file( f ) { file.seek( offset ); diff --git a/src/chunkedstorage.hh b/src/chunkedstorage.hh index 76e958ed5..29705b496 100644 --- a/src/chunkedstorage.hh +++ b/src/chunkedstorage.hh @@ -31,11 +31,11 @@ DEF_EX( mapFailed, "Failed to map/unmap the file", Ex ) class Writer { vector< uint32_t > offsets; - File::Class & file; + File::Index & file; size_t scratchPadOffset, scratchPadSize; public: - explicit Writer( File::Class & ); + explicit Writer( File::Index & ); /// Starts new block. Returns its address. uint32_t startNewBlock(); @@ -72,12 +72,12 @@ private: class Reader { vector< uint32_t > offsets; - File::Class & file; + File::Index & file; public: /// Creates reader by giving it a file to read from and the offset returned /// by Writer::finish(). - Reader( File::Class &, uint32_t ); + Reader( File::Index &, uint32_t ); /// Reads the block previously written by Writer, identified by its address. /// Uses the user-provided storage to load the entire chunk, and then to diff --git a/src/common/file.cc b/src/common/file.cc index fefa51f58..6c542bbcc 100644 --- a/src/common/file.cc +++ b/src/common/file.cc @@ -35,13 +35,13 @@ bool tryPossibleZipName( std::string const & name, std::string & copyTo ) void loadFromFile( std::string const & filename, std::vector< char > & data ) { - File::Class f( filename, "rb" ); + File::Index f( filename, "rb" ); auto size = f.file().size(); // QFile::size() obtains size via statx on Linux data.resize( size ); f.read( data.data(), size ); } -void Class::open( char const * mode ) +void Index::open( char const * mode ) { QFile::OpenMode openMode = QIODevice::Text; @@ -74,26 +74,26 @@ void Class::open( char const * mode ) throw exCantOpen( f.fileName().toStdString() + ": " + f.errorString().toUtf8().data() ); } -Class::Class( std::string_view filename, char const * mode ) +Index::Index( std::string_view filename, char const * mode ) { f.setFileName( QString::fromUtf8( filename.data(), filename.size() ) ); open( mode ); } -void Class::read( void * buf, qint64 size ) +void Index::read( void * buf, qint64 size ) { if ( f.read( static_cast< char * >( buf ), size ) != size ) { throw exReadError(); } } -size_t Class::readRecords( void * buf, qint64 size, qint64 count ) +size_t Index::readRecords( void * buf, qint64 size, qint64 count ) { qint64 result = f.read( static_cast< char * >( buf ), size * count ); return result < 0 ? result : result / size; } -void Class::write( void const * buf, qint64 size ) +void Index::write( void const * buf, qint64 size ) { if ( 0 == size ) { return; @@ -106,13 +106,13 @@ void Class::write( void const * buf, qint64 size ) f.write( static_cast< char const * >( buf ), size ); } -size_t Class::writeRecords( void const * buf, qint64 size, qint64 count ) +size_t Index::writeRecords( void const * buf, qint64 size, qint64 count ) { qint64 result = f.write( static_cast< const char * >( buf ), size * count ); return result < 0 ? result : result / size; } -char * Class::gets( char * s, int size, bool stripNl ) +char * Index::gets( char * s, int size, bool stripNl ) { qint64 len = f.readLine( s, size ); char * result = len > 0 ? s : nullptr; @@ -134,7 +134,7 @@ char * Class::gets( char * s, int size, bool stripNl ) return result; } -std::string Class::gets( bool stripNl ) +std::string Index::gets( bool stripNl ) { char buf[ 1024 ]; @@ -144,61 +144,61 @@ std::string Class::gets( bool stripNl ) return { buf }; } -QByteArray Class::readall() +QByteArray Index::readall() { return f.readAll(); }; -void Class::seek( qint64 offset ) +void Index::seek( qint64 offset ) { if ( !f.seek( offset ) ) throw exSeekError(); } -uchar * Class::map( qint64 offset, qint64 size ) +uchar * Index::map( qint64 offset, qint64 size ) { return f.map( offset, size ); } -bool Class::unmap( uchar * address ) +bool Index::unmap( uchar * address ) { return f.unmap( address ); } -void Class::seekEnd() +void Index::seekEnd() { if ( !f.seek( f.size() ) ) throw exSeekError(); } -void Class::rewind() +void Index::rewind() { seek( 0 ); } -qint64 Class::tell() +qint64 Index::tell() { return f.pos(); } -bool Class::eof() const +bool Index::eof() const { return f.atEnd(); } -QFile & Class::file() +QFile & Index::file() { return f; } -void Class::close() +void Index::close() { f.close(); } -Class::~Class() noexcept +Index::~Index() noexcept { f.close(); } diff --git a/src/common/file.hh b/src/common/file.hh index c6b08fbab..0cfc0017c 100644 --- a/src/common/file.hh +++ b/src/common/file.hh @@ -13,12 +13,7 @@ #include #include -/// A wrapper over QFile with some GD specific functions -/// and exception throwing which are required for older coded to work correctly -/// Consider the wrapped QFile as private implementation in the `Pimpl Idiom` -/// -/// Note: this is used *only* in code related to `Dictionary::CLass` to manage dict files. -/// In other places, just use QFile directly. +/// File utilities namespace File { DEF_EX( Ex, "File exception", std::exception ) @@ -40,7 +35,8 @@ inline bool exists( std::string_view filename ) noexcept return QFileInfo::exists( QString::fromUtf8( filename.data(), filename.size() ) ); }; -class Class +/// Exclusivly used for processing GD's index files +class Index { QFile f; @@ -48,7 +44,7 @@ public: QMutex lock; // Create QFile Object and open() it. - Class( std::string_view filename, char const * mode ); + Index( std::string_view filename, char const * mode ); /// QFile::read & QFile::write , but with exception throwing void read( void * buf, qint64 size ); @@ -115,7 +111,7 @@ public: /// Closes the file. No further operations are valid. void close(); - ~Class() noexcept; + ~Index() noexcept; private: // QFile::open but with fopen-like mode settings. diff --git a/src/dict/aard.cc b/src/dict/aard.cc index 4aefd3b15..e713b86d3 100644 --- a/src/dict/aard.cc +++ b/src/dict/aard.cc @@ -119,7 +119,7 @@ __attribute__( ( packed ) ) bool indexIsOldOrBad( string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -214,10 +214,10 @@ class AardDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex; QMutex aardMutex; - File::Class idx; + File::Index idx; IdxHeader idxHeader; ChunkedStorage::Reader chunks; - File::Class df; + File::Index df; public: @@ -774,7 +774,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f } } - File::Class df( fileName, "rb" ); + File::Index df( fileName, "rb" ); AAR_header dictHeader; @@ -839,7 +839,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f initializing.indexingDictionary( dictName ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; memset( &idxHeader, 0, sizeof( idxHeader ) ); diff --git a/src/dict/bgl.cc b/src/dict/bgl.cc index 6f697dca0..253859965 100644 --- a/src/dict/bgl.cc +++ b/src/dict/bgl.cc @@ -91,7 +91,7 @@ __attribute__( ( packed ) ) bool indexIsOldOrBad( string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -175,7 +175,7 @@ DEF_EX( exChunkIndexOutOfRange, "Chunk index is out of range", Dictionary::Ex ) class BglDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex; - File::Class idx; + File::Index idx; IdxHeader idxHeader; ChunkedStorage::Reader chunks; @@ -809,7 +809,7 @@ class BglResourceRequest: public Dictionary::DataRequest { QMutex & idxMutex; - File::Class & idx; + File::Index & idx; uint32_t resourceListOffset, resourcesCount; string name; @@ -819,7 +819,7 @@ class BglResourceRequest: public Dictionary::DataRequest public: BglResourceRequest( QMutex & idxMutex_, - File::Class & idx_, + File::Index & idx_, uint32_t resourceListOffset_, uint32_t resourcesCount_, string const & name_ ): @@ -953,12 +953,12 @@ void BglDictionary::replaceCharsetEntities( string & text ) class ResourceHandler: public Babylon::ResourceHandler { - File::Class & idxFile; + File::Index & idxFile; list< pair< string, uint32_t > > resources; public: - ResourceHandler( File::Class & idxFile_ ): + ResourceHandler( File::Index & idxFile_ ): idxFile( idxFile_ ) { } @@ -1048,7 +1048,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f initializing.indexingDictionary( b.title() ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; diff --git a/src/dict/dictdfiles.cc b/src/dict/dictdfiles.cc index c2ef09e10..941e5194b 100644 --- a/src/dict/dictdfiles.cc +++ b/src/dict/dictdfiles.cc @@ -73,7 +73,7 @@ __attribute__( ( packed ) ) bool indexIsOldOrBad( string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -84,7 +84,7 @@ bool indexIsOldOrBad( string const & indexFile ) class DictdDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex; - File::Class idx, indexFile; // The later is .index file + File::Index idx, indexFile; // The later is .index file IdxHeader idxHeader; dictData * dz; QMutex indexFileMutex, dzMutex; @@ -579,7 +579,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f initializing.indexingDictionary( dictionaryName ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; @@ -592,7 +592,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f IndexedWords indexedWords; - File::Class indexFile( dictFiles[ 0 ], "rb" ); + File::Index indexFile( dictFiles[ 0 ], "rb" ); // Read words from index until none's left. diff --git a/src/dict/dsl.cc b/src/dict/dsl.cc index 61b63679e..a4a4fd1f0 100644 --- a/src/dict/dsl.cc +++ b/src/dict/dsl.cc @@ -131,7 +131,7 @@ struct InsidedCard bool indexIsOldOrBad( string const & indexFile, bool hasZipFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -143,7 +143,7 @@ bool indexIsOldOrBad( string const & indexFile, bool hasZipFile ) class DslDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex; - File::Class idx; + File::Index idx; IdxHeader idxHeader; sptr< ChunkedStorage::Reader > chunks; string preferredSoundDictionary; @@ -1718,7 +1718,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f gdDebug( "Dsl: Building the index for dictionary: %s\n", QString::fromStdU32String( scanner.getDictionaryName() ).toUtf8().data() ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; diff --git a/src/dict/epwing.cc b/src/dict/epwing.cc index edb3b7451..470f4b51f 100644 --- a/src/dict/epwing.cc +++ b/src/dict/epwing.cc @@ -70,7 +70,7 @@ __attribute__( ( packed ) ) bool indexIsOldOrBad( string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -84,7 +84,7 @@ class EpwingDictionary: public BtreeIndexing::BtreeDictionary Q_DECLARE_TR_FUNCTIONS( Epwing::EpwingDictionary ) QMutex idxMutex; - File::Class idx; + File::Index idx; IdxHeader idxHeader; string bookName; ChunkedStorage::Reader chunks; @@ -1201,7 +1201,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f QByteArray nameData = str.toUtf8(); initializing.indexingDictionary( nameData.data() ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader{}; diff --git a/src/dict/gls.cc b/src/dict/gls.cc index 739296cd9..ed02a580c 100644 --- a/src/dict/gls.cc +++ b/src/dict/gls.cc @@ -332,7 +332,7 @@ __attribute__( ( packed ) ) bool indexIsOldOrBad( string const & indexFile, bool hasZipFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -344,7 +344,7 @@ bool indexIsOldOrBad( string const & indexFile, bool hasZipFile ) class GlsDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex; - File::Class idx; + File::Index idx; IdxHeader idxHeader; dictData * dz; ChunkedStorage::Reader chunks; @@ -1233,7 +1233,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f gdDebug( "Gls: Building the index for dictionary: %s\n", QString::fromStdU32String( scanner.getDictionaryName() ).toUtf8().data() ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; diff --git a/src/dict/lsa.cc b/src/dict/lsa.cc index 2d206a935..644da7652 100644 --- a/src/dict/lsa.cc +++ b/src/dict/lsa.cc @@ -65,7 +65,7 @@ __attribute__( ( packed ) ) bool indexIsOldOrBad( string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -91,10 +91,10 @@ struct Entry public: // Reads an entry from the file's current position - Entry( File::Class & f ); + Entry( File::Index & f ); }; -Entry::Entry( File::Class & f ) +Entry::Entry( File::Index & f ) { bool firstEntry = ( f.tell() == 13 ); // Read the entry's filename @@ -147,7 +147,7 @@ Entry::Entry( File::Class & f ) class LsaDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex; - File::Class idx; + File::Index idx; IdxHeader idxHeader; public: @@ -390,7 +390,7 @@ sptr< Dictionary::DataRequest > LsaDictionary::getResource( string const & name if ( chain.empty() ) return std::make_shared< Dictionary::DataRequestInstant >( false ); // No such resource - File::Class f( getDictionaryFilenames()[ 0 ], "rb" ); + File::Index f( getDictionaryFilenames()[ 0 ], "rb" ); f.seek( chain[ 0 ].articleOffset ); Entry e( f ); @@ -503,7 +503,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f continue; try { - File::Class f( *i, "rb" ); + File::Index f( *i, "rb" ); /// Check the signature @@ -528,7 +528,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f initializing.indexingDictionary( Utils::Fs::basename( *i ) ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; diff --git a/src/dict/mdx.cc b/src/dict/mdx.cc index ba5a50766..da891ee03 100644 --- a/src/dict/mdx.cc +++ b/src/dict/mdx.cc @@ -191,7 +191,7 @@ class IndexedMdd: public BtreeIndexing::BtreeIndex class MdxDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex; - File::Class idx; + File::Index idx; string idxFileName; IdxHeader idxHeader; string encoding; @@ -1220,7 +1220,7 @@ class ResourceHandler: public MdictParser::RecordHandler static bool indexIsOldOrBad( vector< string > const & dictFiles, string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; return idx.readRecords( &header, sizeof( header ), 1 ) != 1 || header.signature != kSignature @@ -1296,7 +1296,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f } } - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; memset( &idxHeader, 0, sizeof( idxHeader ) ); // We write a dummy header first. At the end of the process the header diff --git a/src/dict/sdict.cc b/src/dict/sdict.cc index 589ead36a..d5cb74f71 100644 --- a/src/dict/sdict.cc +++ b/src/dict/sdict.cc @@ -112,7 +112,7 @@ __attribute__( ( packed ) ) bool indexIsOldOrBad( string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -123,10 +123,10 @@ bool indexIsOldOrBad( string const & indexFile ) class SdictDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex, sdictMutex; - File::Class idx; + File::Index idx; IdxHeader idxHeader; ChunkedStorage::Reader chunks; - File::Class df; + File::Index df; // Not an index, uses this type for legacy reasons. public: @@ -676,7 +676,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f try { gdDebug( "SDict: Building the index for dictionary: %s\n", fileName.c_str() ); - File::Class df( fileName, "rb" ); + File::Index df( fileName, "rb" ); DCT_header dictHeader; @@ -706,7 +706,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f initializing.indexingDictionary( dictName ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; memset( &idxHeader, 0, sizeof( idxHeader ) ); diff --git a/src/dict/slob.cc b/src/dict/slob.cc index 4a27469fb..3b35052b3 100644 --- a/src/dict/slob.cc +++ b/src/dict/slob.cc @@ -103,7 +103,7 @@ struct RefEntry bool indexIsOldOrBad( string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -575,7 +575,7 @@ class SlobDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex; QMutex slobMutex, idxResourceMutex; - File::Class idx; + File::Index idx; BtreeIndex resourceIndex; IdxHeader idxHeader; SlobFile sf; @@ -1387,7 +1387,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f initializing.indexingDictionary( sf.getDictionaryName().toUtf8().constData() ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; memset( &idxHeader, 0, sizeof( idxHeader ) ); diff --git a/src/dict/sounddir.cc b/src/dict/sounddir.cc index a61852cd8..4d167fd4a 100644 --- a/src/dict/sounddir.cc +++ b/src/dict/sounddir.cc @@ -53,7 +53,7 @@ __attribute__( ( packed ) ) bool indexIsOldOrBad( string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -65,7 +65,7 @@ class SoundDirDictionary: public BtreeIndexing::BtreeDictionary { string name; QMutex idxMutex; - File::Class idx; + File::Index idx; IdxHeader idxHeader; ChunkedStorage::Reader chunks; QString iconFilename; @@ -362,7 +362,7 @@ sptr< Dictionary::DataRequest > SoundDirDictionary::getResource( string const & // Now try loading that file try { - File::Class f( fileName.toStdString(), "rb" ); + File::Index f( fileName.toStdString(), "rb" ); sptr< Dictionary::DataRequestInstant > dr = std::make_shared< Dictionary::DataRequestInstant >( true ); @@ -463,7 +463,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( Config::SoundDirs const & initializing.indexingDictionary( soundDir.name.toUtf8().data() ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; diff --git a/src/dict/stardict.cc b/src/dict/stardict.cc index a34ae5ab2..a7728d53e 100644 --- a/src/dict/stardict.cc +++ b/src/dict/stardict.cc @@ -90,7 +90,7 @@ struct Ifo string sametypesequence, dicttype, description; string copyright, author, email, website, date; - explicit Ifo( File::Class & ); + explicit Ifo( File::Index & ); }; enum { @@ -123,7 +123,7 @@ __attribute__( ( packed ) ) bool indexIsOldOrBad( string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -134,7 +134,7 @@ bool indexIsOldOrBad( string const & indexFile ) class StardictDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex; - File::Class idx; + File::Index idx; IdxHeader idxHeader; string bookName; string sameTypeSequence; @@ -1039,7 +1039,7 @@ QString const & StardictDictionary::getDescription() if ( !dictionaryDescription.isEmpty() ) return dictionaryDescription; - File::Class ifoFile( getDictionaryFilenames()[ 0 ], "r" ); + File::Index ifoFile( getDictionaryFilenames()[ 0 ], "r" ); Ifo ifo( ifoFile ); if ( !ifo.copyright.empty() ) { @@ -1402,7 +1402,7 @@ static char const * beginsWith( char const * substr, char const * str ) return strncmp( str, substr, len ) == 0 ? str + len : 0; } -Ifo::Ifo( File::Class & f ): +Ifo::Ifo( File::Index & f ): wordcount( 0 ), synwordcount( 0 ), idxfilesize( 0 ), @@ -1809,7 +1809,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) ) { // Building the index - File::Class ifoFile( fileName, "r" ); + File::Index ifoFile( fileName, "r" ); Ifo ifo( ifoFile ); @@ -1840,7 +1840,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f initializing.indexingDictionary( ifo.bookname ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; diff --git a/src/dict/xdxf.cc b/src/dict/xdxf.cc index bb7e40f73..6534f23d7 100644 --- a/src/dict/xdxf.cc +++ b/src/dict/xdxf.cc @@ -129,7 +129,7 @@ __attribute__( ( packed ) ) bool indexIsOldOrBad( string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -141,7 +141,7 @@ bool indexIsOldOrBad( string const & indexFile ) class XdxfDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex; - File::Class idx; + File::Index idx; IdxHeader idxHeader; sptr< ChunkedStorage::Reader > chunks; QMutex dzMutex; @@ -1040,7 +1040,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f //initializing.indexingDictionary( nameFromFileName( dictFiles[ 0 ] ) ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; map< string, string > abrv; diff --git a/src/dict/zim.cc b/src/dict/zim.cc index 9147f72c0..5f5ef0c82 100644 --- a/src/dict/zim.cc +++ b/src/dict/zim.cc @@ -98,7 +98,7 @@ __attribute__( ( packed ) ) // Some supporting functions bool indexIsOldOrBad( string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -162,7 +162,7 @@ class ZimDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex; QMutex zimMutex; - File::Class idx; + File::Index idx; IdxHeader idxHeader; ZimFile df; set< quint32 > articlesIndexedForFTS; @@ -842,7 +842,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f initializing.indexingDictionary( firstName.mid( n + 1 ).toUtf8().constData() ); } - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; memset( &idxHeader, 0, sizeof( idxHeader ) ); idxHeader.namePtr = 0xFFFFFFFF; diff --git a/src/dict/zipsounds.cc b/src/dict/zipsounds.cc index 9b16c53d9..61f60ef3c 100644 --- a/src/dict/zipsounds.cc +++ b/src/dict/zipsounds.cc @@ -61,7 +61,7 @@ __attribute__( ( packed ) ) bool indexIsOldOrBad( string const & indexFile ) { - File::Class idx( indexFile, "rb" ); + File::Index idx( indexFile, "rb" ); IdxHeader header; @@ -98,7 +98,7 @@ wstring stripExtension( string const & str ) class ZipSoundsDictionary: public BtreeIndexing::BtreeDictionary { QMutex idxMutex; - File::Class idx; + File::Index idx; IdxHeader idxHeader; sptr< ChunkedStorage::Reader > chunks; IndexedZip zipsFile; @@ -394,7 +394,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) ) { gdDebug( "Zips: Building the index for dictionary: %s\n", fileName.c_str() ); - File::Class idx( indexFile, "wb" ); + File::Index idx( indexFile, "wb" ); IdxHeader idxHeader; memset( &idxHeader, 0, sizeof( idxHeader ) ); From 49e576d02fb341d9b4b877ad7d0cb669f6c2a711 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Sat, 23 Mar 2024 00:05:13 -0400 Subject: [PATCH 39/67] feat: document Index file in architecture.md --- website/docs/architecture.md | 30 ++++++++++++++++++++++++++++++ website/docs/developer.md | 10 +--------- website/mkdocs.yml | 28 ++++++++++++++-------------- 3 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 website/docs/architecture.md diff --git a/website/docs/architecture.md b/website/docs/architecture.md new file mode 100644 index 000000000..1e93337b0 --- /dev/null +++ b/website/docs/architecture.md @@ -0,0 +1,30 @@ +## Index file + +Each index file have 4 sections. + +1. `IdxHeader` +2. `ExtraInfo` (Being used but unnamed in source code) +3. `Chunks` +4. `BtreeIndex` + +The `IdxHeader` are 32bits blocks of various meta info of the index. The most important info are `chunksOffset` and `indexRootOffset` pointing to the starting offset of `BtreeIndex` and `Chunks`. + +Some dicts only have one `ExtraInfo`: the `dictionaryName` which is an uint32 size of a string followed and the string. + +Each chunk contains uint32 size of uncompressed data, uint32 size of zlib compressed data, and the zlib compressed data. + +The `Chunks` maybe used by both `IdxHeader` and `BtreeIndex`. + +By adding new a new chunk to `Chunks` and store an offset to `IdxHeader`, `ExtraInfo` can store arbitrary long information. + +`BtreeIndex` is a zlib compressed typical btree implementation in which each Node will include `word` info and a `offset` that pointing to corresponding `chunk`'s position. + +Note that a `chunk` only includes necessary data to find an article, and it does not contain the `word`. + +The exact data in `chunk` is decided and interpreted by dictionary implementations. For example, the starting and ending position of an article in a dictionary file. + +## What's under the hood after a word is queried? + +After typing a word into the search box and press enter, the embedded browser will load `gdlookup://localhost?word=`. This url will be handled by Qt webengine's Url Scheme handler. The returned html page will be composed in the ArticleMaker which will initiate some DataRequest on dictionary formats. Resource files will be requested via `bres://` or `qrc://` which will went through a similar process. + +TODO: other subsystems. diff --git a/website/docs/developer.md b/website/docs/developer.md index 256bc202a..9b3d4fb34 100644 --- a/website/docs/developer.md +++ b/website/docs/developer.md @@ -16,12 +16,4 @@ Commit messages should follow [Conventional Commits](https://www.conventionalcom Reformat changes with `clang-format` [how to use clang-format](https://github.com/xiaoyifang/goldendict/blob/staged/howto/how%20to%20use%20.clang-format%20to%20format%20the%20code.md) -Remember to enable `clang-tidy` support on your editor so that `.clang-tidy` will be respected. - -## Architecture - -What's under the hood after a word is queried? - -After typing a word into the search box and press enter, the embedded browser will load `gdlookup://localhost?word=`. This url will be handled by Qt webengine's Url Scheme handler. The returned html page will be composed in the ArticleMaker which will initiate some DataRequest on dictionary formats. Resource files will be requested via `bres://` or `qrc://` which will went through a similar process. - -TODO: other subsystems. \ No newline at end of file +Remember to enable `clang-tidy` support on your editor so that `.clang-tidy` will be respected. \ No newline at end of file diff --git a/website/mkdocs.yml b/website/mkdocs.yml index 171fea724..58a347921 100644 --- a/website/mkdocs.yml +++ b/website/mkdocs.yml @@ -1,4 +1,4 @@ -site_name: GoldenDict-NG +site_name: GoldenDict-ng site_description: GoldenDict-ng is a open source, cross platform, multi formats, feature rich dictionary 是一个开源跨平台支持各种格式的字典程序 site_url: https://xiaoyifang.github.io/goldendict-ng/ @@ -36,8 +36,9 @@ nav: - ToolBar & DictBar: ui_toolbar.md - Favorites: ui_favorites.md - Shortcuts: ui_shortcuts.md - - Special Usages: + - Advanced Usages: - Anki Integration: topic_anki.md + - Program dictionary: howto/how to add a program as dictionary.md - Command Lines: topic_commandline.md - Custom Stylesheet & JavaScript: topic_userstyle.md - Portable Mode: topic_portablemode.md @@ -45,17 +46,16 @@ nav: - Customize Dictionary: custom_dictionary.md - OCR Integration: howto/ocr.md - Wayland: topic_wayland.md + - Debug dictionary JS: howto/how to debug dictionary js.md - Report Bugs & Feedbacks: feedbacks.md - - Contributor Guides: - - Developer: developer.md - - How to: - - Build from source: howto/build_from_source.md - - Customize the opencc: howto/how to customize the opencc.md - - Qt version and github action: howto/how to find out the latest qt version and module in github qt action.md - - Use .clang-format: howto/how to use .clang-format to format the code.md - - Breadpad crash analysis: howto/how to use breadpad crash analysis.md - - Build ffmpeg on Windows: howto/how to build ffmpeg for visual studio.md - - How to update the crowdin.ts file: howto/how to update crowdin.ts file.md - - How to debug dictionary js: howto/how to debug dictionary js.md - - How to add a program dictionary: howto/how to add a program as dictionary.md + - Development Info: + - Start develop: developer.md + - Architecture: architecture.md + - Build from source: howto/build_from_source.md + - Customize the opencc: howto/how to customize the opencc.md + - Qt version and github action: howto/how to find out the latest qt version and module in github qt action.md + - Use .clang-format: howto/how to use .clang-format to format the code.md + - Breadpad crash analysis: howto/how to use breadpad crash analysis.md + - Build ffmpeg on Windows: howto/how to build ffmpeg for visual studio.md + - Update the crowdin.ts file: howto/how to update crowdin.ts file.md From 3187fdeb64636fc1ab40b0ee0c1f79e47faad4e8 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Mon, 25 Mar 2024 08:09:55 -0400 Subject: [PATCH 40/67] clean: utilize QSaveFile and improve config/favorite/history file saving --- goldendict.pro | 2 -- src/common/atomic_rename.cc | 42 ----------------------------------- src/common/atomic_rename.hh | 14 ------------ src/config.cc | 15 ++++--------- src/history.cc | 15 ++++++++----- src/main.cc | 7 ++++-- src/ui/favoritespanewidget.cc | 21 ++++++++++++------ 7 files changed, 32 insertions(+), 84 deletions(-) delete mode 100644 src/common/atomic_rename.cc delete mode 100644 src/common/atomic_rename.hh diff --git a/goldendict.pro b/goldendict.pro index c45fc1572..c4cc13ac6 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -290,7 +290,6 @@ HEADERS += \ src/audioplayerinterface.hh \ src/btreeidx.hh \ src/chunkedstorage.hh \ - src/common/atomic_rename.hh \ src/common/base_type.hh \ src/common/ex.hh \ src/common/file.hh \ @@ -419,7 +418,6 @@ SOURCES += \ src/audioplayerfactory.cc \ src/btreeidx.cc \ src/chunkedstorage.cc \ - src/common/atomic_rename.cc \ src/common/file.cc \ src/common/filetype.cc \ src/common/folding.cc \ diff --git a/src/common/atomic_rename.cc b/src/common/atomic_rename.cc deleted file mode 100644 index d4e0fc1ed..000000000 --- a/src/common/atomic_rename.cc +++ /dev/null @@ -1,42 +0,0 @@ -/* This file is (c) 2008-2012 Konstantin Isakov - * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ - -#include "atomic_rename.hh" -#include -#include -#include // for wchar_t -#include -#include - -#ifdef Q_OS_WIN32 - #include -#endif - -#include - - -bool renameAtomically( QString const & oldName, QString const & newName ) -{ -#ifdef Q_OS_WIN32 - - QString srcFile( QDir::toNativeSeparators( oldName ) ); - QVector< wchar_t > srcFileW( srcFile.size() + 1 ); - srcFileW[ srcFile.toWCharArray( srcFileW.data() ) ] = 0; - - QString destFile( QDir::toNativeSeparators( newName ) ); - QVector< wchar_t > destFileW( destFile.size() + 1 ); - destFileW[ destFile.toWCharArray( destFileW.data() ) ] = 0; - - if ( !MoveFileExW( srcFileW.data(), destFileW.data(), MOVEFILE_REPLACE_EXISTING ) ) - return false; - -#else - - if ( rename( QFile::encodeName( QDir::toNativeSeparators( oldName ) ).data(), - QFile::encodeName( QDir::toNativeSeparators( newName ) ).data() ) ) - return false; - -#endif - - return true; -} diff --git a/src/common/atomic_rename.hh b/src/common/atomic_rename.hh deleted file mode 100644 index 4d28c568c..000000000 --- a/src/common/atomic_rename.hh +++ /dev/null @@ -1,14 +0,0 @@ -/* This file is (c) 2008-2012 Konstantin Isakov - * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ - -#ifndef __ATOMIC_RENAME_HH_INCLUDED__ -#define __ATOMIC_RENAME_HH_INCLUDED__ - -#include - -/// Performs an atomic rename of file, from oldBame to newName. If newName -/// exists, it gets overwritten. Names should feature Qt-style separators -/// (straight slashes). Returns true on success, false on failure. -bool renameAtomically( QString const & oldName, QString const & newName ); - -#endif diff --git a/src/config.cc b/src/config.cc index d81627808..92cafb879 100644 --- a/src/config.cc +++ b/src/config.cc @@ -3,7 +3,7 @@ #include "config.hh" #include "folding.hh" -#include +#include #include #include #include @@ -17,8 +17,6 @@ #include -#include "atomic_rename.hh" - #include #if defined( HAVE_X11 ) @@ -1310,7 +1308,7 @@ void saveGroup( Group const & data, QDomElement & group ) void save( Class const & c ) { - QFile configFile( getConfigFileName() + ".tmp" ); + QSaveFile configFile( getConfigFileName() ); if ( !configFile.open( QFile::WriteOnly ) ) throw exCantWriteConfigFile(); @@ -2233,14 +2231,9 @@ void save( Class const & c ) hd.appendChild( opt ); } - QByteArray result( dd.toByteArray() ); - - if ( configFile.write( result ) != result.size() ) + configFile.write( dd.toByteArray() ); + if ( !configFile.commit() ) throw exCantWriteConfigFile(); - - configFile.close(); - - renameAtomically( configFile.fileName(), getConfigFileName() ); } QString getConfigFileName() diff --git a/src/history.cc b/src/history.cc index 7b1257577..8a4662c82 100644 --- a/src/history.cc +++ b/src/history.cc @@ -3,8 +3,9 @@ #include "history.hh" #include "config.hh" -#include "atomic_rename.hh" #include +#include +#include History::History( unsigned size, unsigned maxItemLength_ ): maxSize( size ), @@ -117,7 +118,7 @@ bool History::save() if ( !dirty ) return true; - QFile file( Config::getHistoryFileName() + ".tmp" ); + QSaveFile file( Config::getHistoryFileName() ); if ( !file.open( QFile::WriteOnly | QIODevice::Text ) ) return false; @@ -136,11 +137,13 @@ bool History::save() return false; } - file.close(); - - dirty = false; + if ( file.commit() ) { + dirty = false; + return true; + } - return renameAtomically( file.fileName(), Config::getHistoryFileName() ); + qDebug() << "Failed to save history file"; + return false; } void History::clear() diff --git a/src/main.cc b/src/main.cc index 8d573e69b..c909b7afa 100644 --- a/src/main.cc +++ b/src/main.cc @@ -21,12 +21,12 @@ #endif #include "termination.hh" -#include "atomic_rename.hh" #include #include #include #include #include +#include #include #include "gddebug.hh" @@ -498,7 +498,10 @@ int main( int argc, char ** argv ) return -1; QString configFile = Config::getConfigFileName(); - renameAtomically( configFile, configFile + ".bad" ); + QFile::rename( configFile, + configFile % QStringLiteral( "." ) + % QDateTime::currentDateTime().toString( QStringLiteral( "yyyyMMdd_HHmmss" ) ) + % QStringLiteral( ".bad" ) ); continue; } break; diff --git a/src/ui/favoritespanewidget.cc b/src/ui/favoritespanewidget.cc index 3aac937f8..788df1748 100644 --- a/src/ui/favoritespanewidget.cc +++ b/src/ui/favoritespanewidget.cc @@ -9,12 +9,15 @@ #include #include #include +#include +#include +#include + #include #include #include "favoritespanewidget.hh" #include "gddebug.hh" -#include "atomic_rename.hh" #include "globalbroadcaster.hh" /************************************************** FavoritesPaneWidget *********************************************/ @@ -624,7 +627,10 @@ void FavoritesModel::readData() dom.clear(); favoritesFile.close(); - renameAtomically( m_favoritesFilename, m_favoritesFilename + ".bak" ); + QFile::rename( m_favoritesFilename, + m_favoritesFilename % QStringLiteral( "." ) + % QDateTime::currentDateTime().toString( QStringLiteral( "yyyyMMdd_HHmmss" ) ) + % QStringLiteral( ".bad" ) ); } else favoritesFile.close(); @@ -642,7 +648,7 @@ void FavoritesModel::saveData() if ( !dirty ) return; - QFile tmpFile( m_favoritesFilename + ".tmp" ); + QSaveFile tmpFile( m_favoritesFilename ); if ( !tmpFile.open( QFile::WriteOnly ) ) { gdWarning( "Can't write favorites file, error: %s", tmpFile.errorString().toUtf8().data() ); return; @@ -661,11 +667,12 @@ void FavoritesModel::saveData() return; } - tmpFile.close(); - - if ( renameAtomically( tmpFile.fileName(), m_favoritesFilename ) ) + if ( tmpFile.commit() ) { dirty = false; - + } + else { + qDebug() << "Failed to save favorite file"; + } dom.clear(); } From 5e60a6b6df9baa6fd7b57597f2a8d0e10b6f588e Mon Sep 17 00:00:00 2001 From: xiaoyifang Date: Wed, 27 Mar 2024 10:32:45 +0800 Subject: [PATCH 41/67] opt: the xapian fts does not concern about the address order --- src/ftshelpers.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ftshelpers.cc b/src/ftshelpers.cc index a84ff3f98..8c42b20f4 100644 --- a/src/ftshelpers.cc +++ b/src/ftshelpers.cc @@ -91,7 +91,7 @@ void makeFTSIndex( BtreeIndexing::BtreeDictionary * dict, QAtomicInt & isCancell if ( Utils::AtomicInt::loadAcquire( isCancelled ) ) throw exUserAbort(); - dict->sortArticlesOffsetsForFTS( offsets, isCancelled ); + // dict->sortArticlesOffsetsForFTS( offsets, isCancelled ); // incremental build the index. // get the last address. From 077fe0a443a52c35b40eb7da431ecb5f525713ee Mon Sep 17 00:00:00 2001 From: xiaoyifang Date: Wed, 27 Mar 2024 15:09:26 +0800 Subject: [PATCH 42/67] action: add beta branch to the workflow --- .github/workflows/AutoTag.yml | 1 + .github/workflows/cmake build check.yml | 1 + .github/workflows/macos-arm-homebrew.yml | 1 + .github/workflows/macos-homebrew-PR-check.yml | 1 + .github/workflows/macos-homebrew.yml | 1 + .github/workflows/ubuntu-PR-check.yml | 1 + .github/workflows/windows-6.x.yml | 1 + .github/workflows/windows-PR-check.yml | 1 + 8 files changed, 8 insertions(+) diff --git a/.github/workflows/AutoTag.yml b/.github/workflows/AutoTag.yml index 02f147a59..6fb02f100 100644 --- a/.github/workflows/AutoTag.yml +++ b/.github/workflows/AutoTag.yml @@ -8,6 +8,7 @@ on: branches: - dev - master + - experimental paths-ignore: - 'docs/**' diff --git a/.github/workflows/cmake build check.yml b/.github/workflows/cmake build check.yml index 8d93fe3f1..08a17fe2d 100644 --- a/.github/workflows/cmake build check.yml +++ b/.github/workflows/cmake build check.yml @@ -8,6 +8,7 @@ on: branches: - dev - master + - experimental - staged paths-ignore: - 'docs/**' diff --git a/.github/workflows/macos-arm-homebrew.yml b/.github/workflows/macos-arm-homebrew.yml index fd76c2bd6..425271022 100644 --- a/.github/workflows/macos-arm-homebrew.yml +++ b/.github/workflows/macos-arm-homebrew.yml @@ -8,6 +8,7 @@ on: branches: - dev - master + - experimental # - staged paths-ignore: - 'docs/**' diff --git a/.github/workflows/macos-homebrew-PR-check.yml b/.github/workflows/macos-homebrew-PR-check.yml index ecd735fe0..52e9ae004 100644 --- a/.github/workflows/macos-homebrew-PR-check.yml +++ b/.github/workflows/macos-homebrew-PR-check.yml @@ -10,6 +10,7 @@ on: branches: - dev - master + - experimental - staged paths-ignore: - 'docs/**' diff --git a/.github/workflows/macos-homebrew.yml b/.github/workflows/macos-homebrew.yml index ab55bb707..61849a757 100644 --- a/.github/workflows/macos-homebrew.yml +++ b/.github/workflows/macos-homebrew.yml @@ -8,6 +8,7 @@ on: branches: - dev - master + - experimental # - staged paths-ignore: - 'docs/**' diff --git a/.github/workflows/ubuntu-PR-check.yml b/.github/workflows/ubuntu-PR-check.yml index f5613efa3..5dbd1aee1 100644 --- a/.github/workflows/ubuntu-PR-check.yml +++ b/.github/workflows/ubuntu-PR-check.yml @@ -10,6 +10,7 @@ on: branches: - dev - master + - experimental - staged paths-ignore: - 'docs/**' diff --git a/.github/workflows/windows-6.x.yml b/.github/workflows/windows-6.x.yml index 8c06fd3c5..6267a4711 100644 --- a/.github/workflows/windows-6.x.yml +++ b/.github/workflows/windows-6.x.yml @@ -11,6 +11,7 @@ on: branches: - dev - master + - experimental # - staged paths-ignore: - 'docs/**' diff --git a/.github/workflows/windows-PR-check.yml b/.github/workflows/windows-PR-check.yml index fc23e4837..ea3d8abc5 100644 --- a/.github/workflows/windows-PR-check.yml +++ b/.github/workflows/windows-PR-check.yml @@ -10,6 +10,7 @@ on: branches: - dev - master + - experimental - staged paths-ignore: - 'docs/**' From 327aff93904960d374f9d2e0a8cf0eefaa99ef33 Mon Sep 17 00:00:00 2001 From: xiaoyifang Date: Wed, 27 Mar 2024 17:59:47 +0800 Subject: [PATCH 43/67] action: use clang-format in experimental branch --- .github/workflows/auto format.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/auto format.yml b/.github/workflows/auto format.yml index 814538d0f..590b9f333 100644 --- a/.github/workflows/auto format.yml +++ b/.github/workflows/auto format.yml @@ -6,6 +6,7 @@ on: # - dev # - master - staged + - experimental paths-ignore: - "docs/**" # - ".github/**" From b9dd9feb04d0ffeb6ad569c3303a575acac88f80 Mon Sep 17 00:00:00 2001 From: xiaoyifang <105986+xiaoyifang@users.noreply.github.com> Date: Thu, 28 Mar 2024 05:08:03 +0800 Subject: [PATCH 44/67] New translations Russian from Crowdin --- locale/ru_RU.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale/ru_RU.ts b/locale/ru_RU.ts index 9276e2cd8..1842ccf48 100644 --- a/locale/ru_RU.ts +++ b/locale/ru_RU.ts @@ -91,7 +91,7 @@ <h3 align="center">Welcome to <b>GoldenDict</b>!</h3><p>To start working with the program, first visit <em>Edit | Dictionaries</em> to add some directory paths where to search for the dictionary files, set up various Wikipedia sites or other sources, adjust dictionary order or create dictionary groups.<p>And then you're ready to look up your words! You can do that in this window by using a pane to the left, or you can <a href="https://xiaoyifang.github.io/goldendict-ng/ui_popup/">look up words from other active applications</a>. <p>To customize program, check out the available preferences at <em>Edit | Preferences</em>. All settings there have tooltips, be sure to read them if you are in doubt about anything.<p>Should you need further help, have any questions, suggestions or just wonder what the others think, you are welcome at the program's <a href="https://github.com/xiaoyifang/goldendict/discussions">forum</a>.<p>Check program's <a href="https://github.com/xiaoyifang/goldendict">website</a> for the updates. <p>(c) 2008-2013 Konstantin Isakov. Licensed under GPLv3 or later. - <h3 align="center">Добро пожаловать в <b>GoldenDict</b>!</h3><p>Чтобы начать работу с программой, сначала зайдите на сайт <em>Редактировать | Словари</em> , чтобы добавить пути к каталогам для поиска файлов словарей, настроить различные сайты Википедии или другие источники, настроить порядок словарей или создать группы словарей.<p>И тогда вы'готовы искать свои слова! Вы можете сделать это в этом окне, используя панель слева, или вы можете <a href="https://xiaoyifang.github.io/goldendict-ng/ui_popup/">искать слова из других активных приложений</a>. <p>Чтобы настроить программу, проверьте доступные настройки на <em>Редактировать | Предпочтения</em>. Все настройки там имеют подсказки, обязательно прочтите их, если у вас есть в чем-то сомнения.<p>Если вам нужна дополнительная помощь, у вас есть какие-либо вопросы, предложения или просто интересно, что думают другие, добро пожаловать на форум программы's <a href="https://github.com/xiaoyifang/goldendict/discussions"></a>.<p>Проверьте наличие обновлений на веб-сайте программы's <a href="https://github.com/xiaoyifang/goldendict"></a> . <p>(c) 2008-2013 Константин Исаков. Лицензия GPLv3 или более поздняя. + <h3 align="center">Добро пожаловать в <b>GoldenDict</b>!</h3><p>Чтобы начать работу с программой, сначала откройте меню <em>Правка | Словари</em>, чтобы добавить пути к каталогам со словарями, к Википедии и другим сайтам, упорядочить и сгруппировать словари.<p>И тогда вы сможете искать слова! Это можно делать непосредственно в этом окне, используя панель поиска слева, или <a href="https://xiaoyifang.github.io/goldendict-ng/ui_popup/">отправляя слова из других программ</a>. <p>Чтобы настроить GoldenDict, перейдите в настройки на <em>Правка | Настройки</em>. Все опции имеют подсказки: обязательно прочтите их, если у вас есть сомнения.<p>Если вам нужна помощь, или у вас есть какие-либо вопросы, предложения или просто интересно, что думают другие, заходите на форум GoldenDict's <a href="https://github.com/xiaoyifang/goldendict/discussions"></a>.<p> Проверить обновления можно на сайте <a href="https://github.com/xiaoyifang/goldendict"></a> . <p>(c) 2008-2013 Константин Исаков. Лицензия GPLv3 или более поздняя. From 4efb4ee8a7dd2474a4cd1749154e0e31cacb25bd Mon Sep 17 00:00:00 2001 From: atauzki Date: Mon, 1 Apr 2024 10:21:01 +0800 Subject: [PATCH 45/67] A temporary workaround to #1451 --- CMake_Win.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMake_Win.cmake b/CMake_Win.cmake index 5b96c257f..354edd89a 100644 --- a/CMake_Win.cmake +++ b/CMake_Win.cmake @@ -28,7 +28,7 @@ target_link_libraries(${GOLDENDICT} PRIVATE ${THIRD_PARTY_LIBARY}) # Copy .dlls to output dir -file(GLOB DLL_FILES LIST_DIRECTORIES false "${CMAKE_SOURCE_DIR}/winlibs/lib/msvc/*.dll") +file(GLOB DLL_FILES LIST_DIRECTORIES false "${CMAKE_SOURCE_DIR}/winlibs/lib/msvc/*.dll" "${Qt6_ROOT}/bin/av*.dll" "${Qt6_ROOT}/bin/sw*.dll") foreach (A_DLL_FILE ${DLL_FILES}) get_filename_component(TEMP_VAR_HOLDING_DLL_FILENAME ${A_DLL_FILE} NAME) configure_file("${A_DLL_FILE}" "${GD_WIN_OUTPUT_DIR}/${TEMP_VAR_HOLDING_DLL_FILENAME}" COPYONLY) From 0b47a9d69c57190292eefdcd3308c9c52a18a3ef Mon Sep 17 00:00:00 2001 From: atauzki Date: Mon, 1 Apr 2024 10:42:10 +0800 Subject: [PATCH 46/67] make github actions pack ffmpeg dll if necessary. #1451 --- .github/scripts/windows-publish.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/scripts/windows-publish.ps1 b/.github/scripts/windows-publish.ps1 index 5d2ec84c1..669f07612 100644 --- a/.github/scripts/windows-publish.ps1 +++ b/.github/scripts/windows-publish.ps1 @@ -70,6 +70,14 @@ function Main() { # Copy-Item -Path $multimedia -Destination $archiveName\plugins -Recurse # } + $multimedia_ffmpeg_av_dll="{0}\bin\av*.dll" -f $env:QTDIR.Trim() + $multimedia_ffmpeg_sw_dll="{0}\bin\sw*.dll" -f $env:QTDIR.Trim() + if (Test-Path $multimedia_ffmpeg_av_dll && Test-Path $multimedia_ffmpeg_sw_dll) { + Write-Host "copy multimedia_ffmpeg_dlls $($multimedia_ffmpeg_av_dll) $($multimedia_ffmpeg_sw_dll) from qt" + Copy-Item -Path $multimedia_ffmpeg_av_dll -Destination $archiveName\plugins -Recurse + Copy-Item -Path $multimedia_ffmpeg_sw_dll -Destination $archiveName\plugins -Recurse + } + Write-Host "compress zip..." # 打包zip Compress-Archive -Path $archiveName -DestinationPath $archiveName'.zip' From 5f98085b814b92f0a70db0380322934da9c33c3f Mon Sep 17 00:00:00 2001 From: atauzki Date: Mon, 1 Apr 2024 10:48:52 +0800 Subject: [PATCH 47/67] Qt version bump. --- .github/workflows/windows-6.x.yml | 2 +- .github/workflows/windows-PR-check.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows-6.x.yml b/.github/workflows/windows-6.x.yml index 6267a4711..03274d514 100644 --- a/.github/workflows/windows-6.x.yml +++ b/.github/workflows/windows-6.x.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: os: [windows-2019] - qt_ver: [ 6.6.2 ] + qt_ver: [ 6.6.2,6.7.0 ] qt_arch: [win64_msvc2019_64] env: targetName: GoldenDict.exe diff --git a/.github/workflows/windows-PR-check.yml b/.github/workflows/windows-PR-check.yml index ea3d8abc5..69f34d2c7 100644 --- a/.github/workflows/windows-PR-check.yml +++ b/.github/workflows/windows-PR-check.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: os: [windows-2019] - qt_ver: [5.15.2,6.6.0] + qt_ver: [5.15.2,6.6.2] qt_arch: [win64_msvc2019_64] steps: - uses: actions/setup-python@v3 From acc62500f429f5d20f1a5810194e8935e953b1f8 Mon Sep 17 00:00:00 2001 From: atauzki Date: Mon, 1 Apr 2024 17:41:00 +0800 Subject: [PATCH 48/67] fix: ffmpeg dll install path --- .github/scripts/windows-publish.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/windows-publish.ps1 b/.github/scripts/windows-publish.ps1 index 669f07612..9aedcbf33 100644 --- a/.github/scripts/windows-publish.ps1 +++ b/.github/scripts/windows-publish.ps1 @@ -74,8 +74,8 @@ function Main() { $multimedia_ffmpeg_sw_dll="{0}\bin\sw*.dll" -f $env:QTDIR.Trim() if (Test-Path $multimedia_ffmpeg_av_dll && Test-Path $multimedia_ffmpeg_sw_dll) { Write-Host "copy multimedia_ffmpeg_dlls $($multimedia_ffmpeg_av_dll) $($multimedia_ffmpeg_sw_dll) from qt" - Copy-Item -Path $multimedia_ffmpeg_av_dll -Destination $archiveName\plugins -Recurse - Copy-Item -Path $multimedia_ffmpeg_sw_dll -Destination $archiveName\plugins -Recurse + Copy-Item -Path $multimedia_ffmpeg_av_dll -Destination $archiveName\ + Copy-Item -Path $multimedia_ffmpeg_sw_dll -Destination $archiveName\ } Write-Host "compress zip..." From e62dded0f62c143406df775b604e86cb933d92ee Mon Sep 17 00:00:00 2001 From: Boyuan Yang Date: Mon, 1 Apr 2024 18:40:01 -0400 Subject: [PATCH 49/67] CMakeLists.txt: Enforce UTC in timestamp generation As discussed in the Debian bug report [2], the current timestamp generation may not be reproducible due to not enforcing the time zone. This patch enforces UTC as time zone information, which will satisfy the reproducible-builds[1] requirement. [1] https://reproducible-builds.org/ [2] https://bugs.debian.org/1068176 Signed-off-by: Boyuan Yang --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d6ddc35cf..9b97b7efa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,7 @@ set(CMAKE_AUTORCC ON) # not included in the qt_standard_project_setup #### Things required during configuration block() # generate version.txt - string(TIMESTAMP build_time) + string(TIMESTAMP build_time UTC) find_package(Git) if (EXISTS "${CMAKE_SOURCE_DIR}/.git" AND GIT_FOUND) execute_process( From 8fcefdaf57eb5f22fc1b389d74a7bd4e494c0cdf Mon Sep 17 00:00:00 2001 From: Dmitry Atamanov Date: Thu, 4 Apr 2024 17:44:26 +0500 Subject: [PATCH 50/67] Update libraries: `fmt` to 10.2.1 and `toml++` to 3.4.0 --- src/metadata.cc | 2 +- thirdparty/fmt/README_about_upgrade.md | 2 +- thirdparty/fmt/include/fmt/args.h | 235 ++ thirdparty/fmt/include/fmt/chrono.h | 2240 +++++++++++++++++ thirdparty/fmt/include/fmt/color.h | 643 +++++ thirdparty/fmt/include/fmt/compile.h | 126 +- thirdparty/fmt/include/fmt/core.h | 638 ++--- thirdparty/fmt/include/fmt/format-inl.h | 189 +- thirdparty/fmt/include/fmt/format.h | 1052 ++++---- thirdparty/fmt/include/fmt/os.h | 455 ++++ thirdparty/fmt/include/fmt/ostream.h | 245 ++ thirdparty/fmt/include/fmt/printf.h | 266 +- thirdparty/fmt/include/fmt/ranges.h | 738 ++++++ thirdparty/fmt/include/fmt/std.h | 537 ++++ thirdparty/fmt/include/fmt/xchar.h | 259 ++ .../tomlplusplus/toml++/{toml.h => toml.hpp} | 844 +++++-- 16 files changed, 6948 insertions(+), 1523 deletions(-) create mode 100644 thirdparty/fmt/include/fmt/args.h create mode 100644 thirdparty/fmt/include/fmt/chrono.h create mode 100644 thirdparty/fmt/include/fmt/color.h create mode 100644 thirdparty/fmt/include/fmt/os.h create mode 100644 thirdparty/fmt/include/fmt/ostream.h create mode 100644 thirdparty/fmt/include/fmt/ranges.h create mode 100644 thirdparty/fmt/include/fmt/std.h create mode 100644 thirdparty/fmt/include/fmt/xchar.h rename thirdparty/tomlplusplus/toml++/{toml.h => toml.hpp} (95%) diff --git a/src/metadata.cc b/src/metadata.cc index 582a9d828..880c77d76 100644 --- a/src/metadata.cc +++ b/src/metadata.cc @@ -1,5 +1,5 @@ #include "metadata.hh" -#include "toml++/toml.h" +#include "toml++/toml.hpp" #include #include diff --git a/thirdparty/fmt/README_about_upgrade.md b/thirdparty/fmt/README_about_upgrade.md index 75d3e1050..a0051d9a5 100644 --- a/thirdparty/fmt/README_about_upgrade.md +++ b/thirdparty/fmt/README_about_upgrade.md @@ -1,4 +1,4 @@ -This dir includes fmt 10.0.0 (as of May 2023) +This dir includes fmt 10.2.1 (as of January 2024) --- diff --git a/thirdparty/fmt/include/fmt/args.h b/thirdparty/fmt/include/fmt/args.h new file mode 100644 index 000000000..ad1654bbb --- /dev/null +++ b/thirdparty/fmt/include/fmt/args.h @@ -0,0 +1,235 @@ +// Formatting library for C++ - dynamic argument lists +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_ARGS_H_ +#define FMT_ARGS_H_ + +#include // std::reference_wrapper +#include // std::unique_ptr +#include + +#include "core.h" + +FMT_BEGIN_NAMESPACE + +namespace detail { + +template struct is_reference_wrapper : std::false_type {}; +template +struct is_reference_wrapper> : std::true_type {}; + +template auto unwrap(const T& v) -> const T& { return v; } +template +auto unwrap(const std::reference_wrapper& v) -> const T& { + return static_cast(v); +} + +class dynamic_arg_list { + // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for + // templates it doesn't complain about inability to deduce single translation + // unit for placing vtable. So storage_node_base is made a fake template. + template struct node { + virtual ~node() = default; + std::unique_ptr> next; + }; + + template struct typed_node : node<> { + T value; + + template + FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} + + template + FMT_CONSTEXPR typed_node(const basic_string_view& arg) + : value(arg.data(), arg.size()) {} + }; + + std::unique_ptr> head_; + + public: + template auto push(const Arg& arg) -> const T& { + auto new_node = std::unique_ptr>(new typed_node(arg)); + auto& value = new_node->value; + new_node->next = std::move(head_); + head_ = std::move(new_node); + return value; + } +}; +} // namespace detail + +/** + \rst + A dynamic version of `fmt::format_arg_store`. + It's equipped with a storage to potentially temporary objects which lifetimes + could be shorter than the format arguments object. + + It can be implicitly converted into `~fmt::basic_format_args` for passing + into type-erased formatting functions such as `~fmt::vformat`. + \endrst + */ +template +class dynamic_format_arg_store +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 + // Workaround a GCC template argument substitution bug. + : public basic_format_args +#endif +{ + private: + using char_type = typename Context::char_type; + + template struct need_copy { + static constexpr detail::type mapped_type = + detail::mapped_type_constant::value; + + enum { + value = !(detail::is_reference_wrapper::value || + std::is_same>::value || + std::is_same>::value || + (mapped_type != detail::type::cstring_type && + mapped_type != detail::type::string_type && + mapped_type != detail::type::custom_type)) + }; + }; + + template + using stored_type = conditional_t< + std::is_convertible>::value && + !detail::is_reference_wrapper::value, + std::basic_string, T>; + + // Storage of basic_format_arg must be contiguous. + std::vector> data_; + std::vector> named_info_; + + // Storage of arguments not fitting into basic_format_arg must grow + // without relocation because items in data_ refer to it. + detail::dynamic_arg_list dynamic_args_; + + friend class basic_format_args; + + auto get_types() const -> unsigned long long { + return detail::is_unpacked_bit | data_.size() | + (named_info_.empty() + ? 0ULL + : static_cast(detail::has_named_args_bit)); + } + + auto data() const -> const basic_format_arg* { + return named_info_.empty() ? data_.data() : data_.data() + 1; + } + + template void emplace_arg(const T& arg) { + data_.emplace_back(detail::make_arg(arg)); + } + + template + void emplace_arg(const detail::named_arg& arg) { + if (named_info_.empty()) { + constexpr const detail::named_arg_info* zero_ptr{nullptr}; + data_.insert(data_.begin(), {zero_ptr, 0}); + } + data_.emplace_back(detail::make_arg(detail::unwrap(arg.value))); + auto pop_one = [](std::vector>* data) { + data->pop_back(); + }; + std::unique_ptr>, decltype(pop_one)> + guard{&data_, pop_one}; + named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); + data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; + guard.release(); + } + + public: + constexpr dynamic_format_arg_store() = default; + + /** + \rst + Adds an argument into the dynamic store for later passing to a formatting + function. + + Note that custom types and string types (but not string views) are copied + into the store dynamically allocating memory if necessary. + + **Example**:: + + fmt::dynamic_format_arg_store store; + store.push_back(42); + store.push_back("abc"); + store.push_back(1.5f); + std::string result = fmt::vformat("{} and {} and {}", store); + \endrst + */ + template void push_back(const T& arg) { + if (detail::const_check(need_copy::value)) + emplace_arg(dynamic_args_.push>(arg)); + else + emplace_arg(detail::unwrap(arg)); + } + + /** + \rst + Adds a reference to the argument into the dynamic store for later passing to + a formatting function. + + **Example**:: + + fmt::dynamic_format_arg_store store; + char band[] = "Rolling Stones"; + store.push_back(std::cref(band)); + band[9] = 'c'; // Changing str affects the output. + std::string result = fmt::vformat("{}", store); + // result == "Rolling Scones" + \endrst + */ + template void push_back(std::reference_wrapper arg) { + static_assert( + need_copy::value, + "objects of built-in types and string views are always copied"); + emplace_arg(arg.get()); + } + + /** + Adds named argument into the dynamic store for later passing to a formatting + function. ``std::reference_wrapper`` is supported to avoid copying of the + argument. The name is always copied into the store. + */ + template + void push_back(const detail::named_arg& arg) { + const char_type* arg_name = + dynamic_args_.push>(arg.name).c_str(); + if (detail::const_check(need_copy::value)) { + emplace_arg( + fmt::arg(arg_name, dynamic_args_.push>(arg.value))); + } else { + emplace_arg(fmt::arg(arg_name, arg.value)); + } + } + + /** Erase all elements from the store */ + void clear() { + data_.clear(); + named_info_.clear(); + dynamic_args_ = detail::dynamic_arg_list(); + } + + /** + \rst + Reserves space to store at least *new_cap* arguments including + *new_cap_named* named arguments. + \endrst + */ + void reserve(size_t new_cap, size_t new_cap_named) { + FMT_ASSERT(new_cap >= new_cap_named, + "Set of arguments includes set of named arguments"); + data_.reserve(new_cap); + named_info_.reserve(new_cap_named); + } +}; + +FMT_END_NAMESPACE + +#endif // FMT_ARGS_H_ diff --git a/thirdparty/fmt/include/fmt/chrono.h b/thirdparty/fmt/include/fmt/chrono.h new file mode 100644 index 000000000..9d54574e1 --- /dev/null +++ b/thirdparty/fmt/include/fmt/chrono.h @@ -0,0 +1,2240 @@ +// Formatting library for C++ - chrono support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_CHRONO_H_ +#define FMT_CHRONO_H_ + +#include +#include +#include // std::isfinite +#include // std::memcpy +#include +#include +#include +#include +#include + +#include "ostream.h" // formatbuf + +FMT_BEGIN_NAMESPACE + +// Check if std::chrono::local_t is available. +#ifndef FMT_USE_LOCAL_TIME +# ifdef __cpp_lib_chrono +# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L) +# else +# define FMT_USE_LOCAL_TIME 0 +# endif +#endif + +// Check if std::chrono::utc_timestamp is available. +#ifndef FMT_USE_UTC_TIME +# ifdef __cpp_lib_chrono +# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L) +# else +# define FMT_USE_UTC_TIME 0 +# endif +#endif + +// Enable tzset. +#ifndef FMT_USE_TZSET +// UWP doesn't provide _tzset. +# if FMT_HAS_INCLUDE("winapifamily.h") +# include +# endif +# if defined(_WIN32) && (!defined(WINAPI_FAMILY) || \ + (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) +# define FMT_USE_TZSET 1 +# else +# define FMT_USE_TZSET 0 +# endif +#endif + +// Enable safe chrono durations, unless explicitly disabled. +#ifndef FMT_SAFE_DURATION_CAST +# define FMT_SAFE_DURATION_CAST 1 +#endif +#if FMT_SAFE_DURATION_CAST + +// For conversion between std::chrono::durations without undefined +// behaviour or erroneous results. +// This is a stripped down version of duration_cast, for inclusion in fmt. +// See https://github.com/pauldreik/safe_duration_cast +// +// Copyright Paul Dreik 2019 +namespace safe_duration_cast { + +template ::value && + std::numeric_limits::is_signed == + std::numeric_limits::is_signed)> +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { + ec = 0; + using F = std::numeric_limits; + using T = std::numeric_limits; + static_assert(F::is_integer, "From must be integral"); + static_assert(T::is_integer, "To must be integral"); + + // A and B are both signed, or both unsigned. + if (detail::const_check(F::digits <= T::digits)) { + // From fits in To without any problem. + } else { + // From does not always fit in To, resort to a dynamic check. + if (from < (T::min)() || from > (T::max)()) { + // outside range. + ec = 1; + return {}; + } + } + return static_cast(from); +} + +/** + * converts From to To, without loss. If the dynamic value of from + * can't be converted to To without loss, ec is set. + */ +template ::value && + std::numeric_limits::is_signed != + std::numeric_limits::is_signed)> +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { + ec = 0; + using F = std::numeric_limits; + using T = std::numeric_limits; + static_assert(F::is_integer, "From must be integral"); + static_assert(T::is_integer, "To must be integral"); + + if (detail::const_check(F::is_signed && !T::is_signed)) { + // From may be negative, not allowed! + if (fmt::detail::is_negative(from)) { + ec = 1; + return {}; + } + // From is positive. Can it always fit in To? + if (detail::const_check(F::digits > T::digits) && + from > static_cast(detail::max_value())) { + ec = 1; + return {}; + } + } + + if (detail::const_check(!F::is_signed && T::is_signed && + F::digits >= T::digits) && + from > static_cast(detail::max_value())) { + ec = 1; + return {}; + } + return static_cast(from); // Lossless conversion. +} + +template ::value)> +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { + ec = 0; + return from; +} // function + +// clang-format off +/** + * converts From to To if possible, otherwise ec is set. + * + * input | output + * ---------------------------------|--------------- + * NaN | NaN + * Inf | Inf + * normal, fits in output | converted (possibly lossy) + * normal, does not fit in output | ec is set + * subnormal | best effort + * -Inf | -Inf + */ +// clang-format on +template ::value)> +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { + ec = 0; + using T = std::numeric_limits; + static_assert(std::is_floating_point::value, "From must be floating"); + static_assert(std::is_floating_point::value, "To must be floating"); + + // catch the only happy case + if (std::isfinite(from)) { + if (from >= T::lowest() && from <= (T::max)()) { + return static_cast(from); + } + // not within range. + ec = 1; + return {}; + } + + // nan and inf will be preserved + return static_cast(from); +} // function + +template ::value)> +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { + ec = 0; + static_assert(std::is_floating_point::value, "From must be floating"); + return from; +} + +/** + * safe duration cast between integral durations + */ +template ::value), + FMT_ENABLE_IF(std::is_integral::value)> +auto safe_duration_cast(std::chrono::duration from, + int& ec) -> To { + using From = std::chrono::duration; + ec = 0; + // the basic idea is that we need to convert from count() in the from type + // to count() in the To type, by multiplying it with this: + struct Factor + : std::ratio_divide {}; + + static_assert(Factor::num > 0, "num must be positive"); + static_assert(Factor::den > 0, "den must be positive"); + + // the conversion is like this: multiply from.count() with Factor::num + // /Factor::den and convert it to To::rep, all this without + // overflow/underflow. let's start by finding a suitable type that can hold + // both To, From and Factor::num + using IntermediateRep = + typename std::common_type::type; + + // safe conversion to IntermediateRep + IntermediateRep count = + lossless_integral_conversion(from.count(), ec); + if (ec) return {}; + // multiply with Factor::num without overflow or underflow + if (detail::const_check(Factor::num != 1)) { + const auto max1 = detail::max_value() / Factor::num; + if (count > max1) { + ec = 1; + return {}; + } + const auto min1 = + (std::numeric_limits::min)() / Factor::num; + if (detail::const_check(!std::is_unsigned::value) && + count < min1) { + ec = 1; + return {}; + } + count *= Factor::num; + } + + if (detail::const_check(Factor::den != 1)) count /= Factor::den; + auto tocount = lossless_integral_conversion(count, ec); + return ec ? To() : To(tocount); +} + +/** + * safe duration_cast between floating point durations + */ +template ::value), + FMT_ENABLE_IF(std::is_floating_point::value)> +auto safe_duration_cast(std::chrono::duration from, + int& ec) -> To { + using From = std::chrono::duration; + ec = 0; + if (std::isnan(from.count())) { + // nan in, gives nan out. easy. + return To{std::numeric_limits::quiet_NaN()}; + } + // maybe we should also check if from is denormal, and decide what to do about + // it. + + // +-inf should be preserved. + if (std::isinf(from.count())) { + return To{from.count()}; + } + + // the basic idea is that we need to convert from count() in the from type + // to count() in the To type, by multiplying it with this: + struct Factor + : std::ratio_divide {}; + + static_assert(Factor::num > 0, "num must be positive"); + static_assert(Factor::den > 0, "den must be positive"); + + // the conversion is like this: multiply from.count() with Factor::num + // /Factor::den and convert it to To::rep, all this without + // overflow/underflow. let's start by finding a suitable type that can hold + // both To, From and Factor::num + using IntermediateRep = + typename std::common_type::type; + + // force conversion of From::rep -> IntermediateRep to be safe, + // even if it will never happen be narrowing in this context. + IntermediateRep count = + safe_float_conversion(from.count(), ec); + if (ec) { + return {}; + } + + // multiply with Factor::num without overflow or underflow + if (detail::const_check(Factor::num != 1)) { + constexpr auto max1 = detail::max_value() / + static_cast(Factor::num); + if (count > max1) { + ec = 1; + return {}; + } + constexpr auto min1 = std::numeric_limits::lowest() / + static_cast(Factor::num); + if (count < min1) { + ec = 1; + return {}; + } + count *= static_cast(Factor::num); + } + + // this can't go wrong, right? den>0 is checked earlier. + if (detail::const_check(Factor::den != 1)) { + using common_t = typename std::common_type::type; + count /= static_cast(Factor::den); + } + + // convert to the to type, safely + using ToRep = typename To::rep; + + const ToRep tocount = safe_float_conversion(count, ec); + if (ec) { + return {}; + } + return To{tocount}; +} +} // namespace safe_duration_cast +#endif + +// Prevents expansion of a preceding token as a function-style macro. +// Usage: f FMT_NOMACRO() +#define FMT_NOMACRO + +namespace detail { +template struct null {}; +inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); } +inline auto localtime_s(...) -> null<> { return null<>(); } +inline auto gmtime_r(...) -> null<> { return null<>(); } +inline auto gmtime_s(...) -> null<> { return null<>(); } + +inline auto get_classic_locale() -> const std::locale& { + static const auto& locale = std::locale::classic(); + return locale; +} + +template struct codecvt_result { + static constexpr const size_t max_size = 32; + CodeUnit buf[max_size]; + CodeUnit* end; +}; + +template +void write_codecvt(codecvt_result& out, string_view in_buf, + const std::locale& loc) { +#if FMT_CLANG_VERSION +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated" + auto& f = std::use_facet>(loc); +# pragma clang diagnostic pop +#else + auto& f = std::use_facet>(loc); +#endif + auto mb = std::mbstate_t(); + const char* from_next = nullptr; + auto result = f.in(mb, in_buf.begin(), in_buf.end(), from_next, + std::begin(out.buf), std::end(out.buf), out.end); + if (result != std::codecvt_base::ok) + FMT_THROW(format_error("failed to format time")); +} + +template +auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) + -> OutputIt { + if (detail::is_utf8() && loc != get_classic_locale()) { + // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and + // gcc-4. +#if FMT_MSC_VERSION != 0 || \ + (defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI)) + // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 + // and newer. + using code_unit = wchar_t; +#else + using code_unit = char32_t; +#endif + + using unit_t = codecvt_result; + unit_t unit; + write_codecvt(unit, in, loc); + // In UTF-8 is used one to four one-byte code units. + auto u = + to_utf8>(); + if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)})) + FMT_THROW(format_error("failed to format time")); + return copy_str(u.c_str(), u.c_str() + u.size(), out); + } + return copy_str(in.data(), in.data() + in.size(), out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + codecvt_result unit; + write_codecvt(unit, sv, loc); + return copy_str(unit.buf, unit.end, out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + return write_encoded_tm_str(out, sv, loc); +} + +template +inline void do_write(buffer& buf, const std::tm& time, + const std::locale& loc, char format, char modifier) { + auto&& format_buf = formatbuf>(buf); + auto&& os = std::basic_ostream(&format_buf); + os.imbue(loc); + const auto& facet = std::use_facet>(loc); + auto end = facet.put(os, os, Char(' '), &time, format, modifier); + if (end.failed()) FMT_THROW(format_error("failed to format time")); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = get_buffer(out); + do_write(buf, time, loc, format, modifier); + return get_iterator(buf, out); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = basic_memory_buffer(); + do_write(buf, time, loc, format, modifier); + return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); +} + +template +struct is_same_arithmetic_type + : public std::integral_constant::value && + std::is_integral::value) || + (std::is_floating_point::value && + std::is_floating_point::value)> { +}; + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(is_same_arithmetic_type::value)> +auto fmt_duration_cast(std::chrono::duration from) -> To { +#if FMT_SAFE_DURATION_CAST + // Throwing version of safe_duration_cast is only available for + // integer to integer or float to float casts. + int ec; + To to = safe_duration_cast::safe_duration_cast(from, ec); + if (ec) FMT_THROW(format_error("cannot format duration")); + return to; +#else + // Standard duration cast, may overflow. + return std::chrono::duration_cast(from); +#endif +} + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(!is_same_arithmetic_type::value)> +auto fmt_duration_cast(std::chrono::duration from) -> To { + // Mixed integer <-> float cast is not supported by safe_duration_cast. + return std::chrono::duration_cast(from); +} + +template +auto to_time_t( + std::chrono::time_point time_point) + -> std::time_t { + // Cannot use std::chrono::system_clock::to_time_t since this would first + // require a cast to std::chrono::system_clock::time_point, which could + // overflow. + return fmt_duration_cast>( + time_point.time_since_epoch()) + .count(); +} +} // namespace detail + +FMT_BEGIN_EXPORT + +/** + Converts given time since epoch as ``std::time_t`` value into calendar time, + expressed in local time. Unlike ``std::localtime``, this function is + thread-safe on most platforms. + */ +inline auto localtime(std::time_t time) -> std::tm { + struct dispatcher { + std::time_t time_; + std::tm tm_; + + dispatcher(std::time_t t) : time_(t) {} + + auto run() -> bool { + using namespace fmt::detail; + return handle(localtime_r(&time_, &tm_)); + } + + auto handle(std::tm* tm) -> bool { return tm != nullptr; } + + auto handle(detail::null<>) -> bool { + using namespace fmt::detail; + return fallback(localtime_s(&tm_, &time_)); + } + + auto fallback(int res) -> bool { return res == 0; } + +#if !FMT_MSC_VERSION + auto fallback(detail::null<>) -> bool { + using namespace fmt::detail; + std::tm* tm = std::localtime(&time_); + if (tm) tm_ = *tm; + return tm != nullptr; + } +#endif + }; + dispatcher lt(time); + // Too big time values may be unsupported. + if (!lt.run()) FMT_THROW(format_error("time_t value out of range")); + return lt.tm_; +} + +#if FMT_USE_LOCAL_TIME +template +inline auto localtime(std::chrono::local_time time) -> std::tm { + return localtime( + detail::to_time_t(std::chrono::current_zone()->to_sys(time))); +} +#endif + +/** + Converts given time since epoch as ``std::time_t`` value into calendar time, + expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this + function is thread-safe on most platforms. + */ +inline auto gmtime(std::time_t time) -> std::tm { + struct dispatcher { + std::time_t time_; + std::tm tm_; + + dispatcher(std::time_t t) : time_(t) {} + + auto run() -> bool { + using namespace fmt::detail; + return handle(gmtime_r(&time_, &tm_)); + } + + auto handle(std::tm* tm) -> bool { return tm != nullptr; } + + auto handle(detail::null<>) -> bool { + using namespace fmt::detail; + return fallback(gmtime_s(&tm_, &time_)); + } + + auto fallback(int res) -> bool { return res == 0; } + +#if !FMT_MSC_VERSION + auto fallback(detail::null<>) -> bool { + std::tm* tm = std::gmtime(&time_); + if (tm) tm_ = *tm; + return tm != nullptr; + } +#endif + }; + auto gt = dispatcher(time); + // Too big time values may be unsupported. + if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); + return gt.tm_; +} + +template +inline auto gmtime( + std::chrono::time_point time_point) + -> std::tm { + return gmtime(detail::to_time_t(time_point)); +} + +namespace detail { + +// Writes two-digit numbers a, b and c separated by sep to buf. +// The method by Pavel Novikov based on +// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/. +inline void write_digit2_separated(char* buf, unsigned a, unsigned b, + unsigned c, char sep) { + unsigned long long digits = + a | (b << 24) | (static_cast(c) << 48); + // Convert each value to BCD. + // We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b. + // The difference is + // y - x = a * 6 + // a can be found from x: + // a = floor(x / 10) + // then + // y = x + a * 6 = x + floor(x / 10) * 6 + // floor(x / 10) is (x * 205) >> 11 (needs 16 bits). + digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6; + // Put low nibbles to high bytes and high nibbles to low bytes. + digits = ((digits & 0x00f00000f00000f0) >> 4) | + ((digits & 0x000f00000f00000f) << 8); + auto usep = static_cast(sep); + // Add ASCII '0' to each digit byte and insert separators. + digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); + + constexpr const size_t len = 8; + if (const_check(is_big_endian())) { + char tmp[len]; + std::memcpy(tmp, &digits, len); + std::reverse_copy(tmp, tmp + len, buf); + } else { + std::memcpy(buf, &digits, len); + } +} + +template +FMT_CONSTEXPR inline auto get_units() -> const char* { + if (std::is_same::value) return "as"; + if (std::is_same::value) return "fs"; + if (std::is_same::value) return "ps"; + if (std::is_same::value) return "ns"; + if (std::is_same::value) return "µs"; + if (std::is_same::value) return "ms"; + if (std::is_same::value) return "cs"; + if (std::is_same::value) return "ds"; + if (std::is_same>::value) return "s"; + if (std::is_same::value) return "das"; + if (std::is_same::value) return "hs"; + if (std::is_same::value) return "ks"; + if (std::is_same::value) return "Ms"; + if (std::is_same::value) return "Gs"; + if (std::is_same::value) return "Ts"; + if (std::is_same::value) return "Ps"; + if (std::is_same::value) return "Es"; + if (std::is_same>::value) return "min"; + if (std::is_same>::value) return "h"; + if (std::is_same>::value) return "d"; + return nullptr; +} + +enum class numeric_system { + standard, + // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. + alternative +}; + +// Glibc extensions for formatting numeric values. +enum class pad_type { + unspecified, + // Do not pad a numeric result string. + none, + // Pad a numeric result string with zeros even if the conversion specifier + // character uses space-padding by default. + zero, + // Pad a numeric result string with spaces. + space, +}; + +template +auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt { + if (pad == pad_type::none) return out; + return std::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); +} + +template +auto write_padding(OutputIt out, pad_type pad) -> OutputIt { + if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0'; + return out; +} + +// Parses a put_time-like format string and invokes handler actions. +template +FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + if (begin == end || *begin == '}') return begin; + if (*begin != '%') FMT_THROW(format_error("invalid format")); + auto ptr = begin; + pad_type pad = pad_type::unspecified; + while (ptr != end) { + auto c = *ptr; + if (c == '}') break; + if (c != '%') { + ++ptr; + continue; + } + if (begin != ptr) handler.on_text(begin, ptr); + ++ptr; // consume '%' + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr; + switch (c) { + case '_': + pad = pad_type::space; + ++ptr; + break; + case '-': + pad = pad_type::none; + ++ptr; + break; + case '0': + pad = pad_type::zero; + ++ptr; + break; + } + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case '%': + handler.on_text(ptr - 1, ptr); + break; + case 'n': { + const Char newline[] = {'\n'}; + handler.on_text(newline, newline + 1); + break; + } + case 't': { + const Char tab[] = {'\t'}; + handler.on_text(tab, tab + 1); + break; + } + // Year: + case 'Y': + handler.on_year(numeric_system::standard); + break; + case 'y': + handler.on_short_year(numeric_system::standard); + break; + case 'C': + handler.on_century(numeric_system::standard); + break; + case 'G': + handler.on_iso_week_based_year(); + break; + case 'g': + handler.on_iso_week_based_short_year(); + break; + // Day of the week: + case 'a': + handler.on_abbr_weekday(); + break; + case 'A': + handler.on_full_weekday(); + break; + case 'w': + handler.on_dec0_weekday(numeric_system::standard); + break; + case 'u': + handler.on_dec1_weekday(numeric_system::standard); + break; + // Month: + case 'b': + case 'h': + handler.on_abbr_month(); + break; + case 'B': + handler.on_full_month(); + break; + case 'm': + handler.on_dec_month(numeric_system::standard); + break; + // Day of the year/month: + case 'U': + handler.on_dec0_week_of_year(numeric_system::standard); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::standard); + break; + case 'V': + handler.on_iso_week_of_year(numeric_system::standard); + break; + case 'j': + handler.on_day_of_year(); + break; + case 'd': + handler.on_day_of_month(numeric_system::standard); + break; + case 'e': + handler.on_day_of_month_space(numeric_system::standard); + break; + // Hour, minute, second: + case 'H': + handler.on_24_hour(numeric_system::standard, pad); + break; + case 'I': + handler.on_12_hour(numeric_system::standard, pad); + break; + case 'M': + handler.on_minute(numeric_system::standard, pad); + break; + case 'S': + handler.on_second(numeric_system::standard, pad); + break; + // Other: + case 'c': + handler.on_datetime(numeric_system::standard); + break; + case 'x': + handler.on_loc_date(numeric_system::standard); + break; + case 'X': + handler.on_loc_time(numeric_system::standard); + break; + case 'D': + handler.on_us_date(); + break; + case 'F': + handler.on_iso_date(); + break; + case 'r': + handler.on_12_hour_time(); + break; + case 'R': + handler.on_24_hour_time(); + break; + case 'T': + handler.on_iso_time(); + break; + case 'p': + handler.on_am_pm(); + break; + case 'Q': + handler.on_duration_value(); + break; + case 'q': + handler.on_duration_unit(); + break; + case 'z': + handler.on_utc_offset(numeric_system::standard); + break; + case 'Z': + handler.on_tz_name(); + break; + // Alternative representation: + case 'E': { + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case 'Y': + handler.on_year(numeric_system::alternative); + break; + case 'y': + handler.on_offset_year(); + break; + case 'C': + handler.on_century(numeric_system::alternative); + break; + case 'c': + handler.on_datetime(numeric_system::alternative); + break; + case 'x': + handler.on_loc_date(numeric_system::alternative); + break; + case 'X': + handler.on_loc_time(numeric_system::alternative); + break; + case 'z': + handler.on_utc_offset(numeric_system::alternative); + break; + default: + FMT_THROW(format_error("invalid format")); + } + break; + } + case 'O': + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case 'y': + handler.on_short_year(numeric_system::alternative); + break; + case 'm': + handler.on_dec_month(numeric_system::alternative); + break; + case 'U': + handler.on_dec0_week_of_year(numeric_system::alternative); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::alternative); + break; + case 'V': + handler.on_iso_week_of_year(numeric_system::alternative); + break; + case 'd': + handler.on_day_of_month(numeric_system::alternative); + break; + case 'e': + handler.on_day_of_month_space(numeric_system::alternative); + break; + case 'w': + handler.on_dec0_weekday(numeric_system::alternative); + break; + case 'u': + handler.on_dec1_weekday(numeric_system::alternative); + break; + case 'H': + handler.on_24_hour(numeric_system::alternative, pad); + break; + case 'I': + handler.on_12_hour(numeric_system::alternative, pad); + break; + case 'M': + handler.on_minute(numeric_system::alternative, pad); + break; + case 'S': + handler.on_second(numeric_system::alternative, pad); + break; + case 'z': + handler.on_utc_offset(numeric_system::alternative); + break; + default: + FMT_THROW(format_error("invalid format")); + } + break; + default: + FMT_THROW(format_error("invalid format")); + } + begin = ptr; + } + if (begin != ptr) handler.on_text(begin, ptr); + return ptr; +} + +template struct null_chrono_spec_handler { + FMT_CONSTEXPR void unsupported() { + static_cast(this)->unsupported(); + } + FMT_CONSTEXPR void on_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_offset_year() { unsupported(); } + FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_short_year() { unsupported(); } + FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); } + FMT_CONSTEXPR void on_full_weekday() { unsupported(); } + FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_abbr_month() { unsupported(); } + FMT_CONSTEXPR void on_full_month() { unsupported(); } + FMT_CONSTEXPR void on_dec_month(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_day_of_year() { unsupported(); } + FMT_CONSTEXPR void on_day_of_month(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_day_of_month_space(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_us_date() { unsupported(); } + FMT_CONSTEXPR void on_iso_date() { unsupported(); } + FMT_CONSTEXPR void on_12_hour_time() { unsupported(); } + FMT_CONSTEXPR void on_24_hour_time() { unsupported(); } + FMT_CONSTEXPR void on_iso_time() { unsupported(); } + FMT_CONSTEXPR void on_am_pm() { unsupported(); } + FMT_CONSTEXPR void on_duration_value() { unsupported(); } + FMT_CONSTEXPR void on_duration_unit() { unsupported(); } + FMT_CONSTEXPR void on_utc_offset(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_tz_name() { unsupported(); } +}; + +struct tm_format_checker : null_chrono_spec_handler { + FMT_NORETURN void unsupported() { FMT_THROW(format_error("no format")); } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_year(numeric_system) {} + FMT_CONSTEXPR void on_short_year(numeric_system) {} + FMT_CONSTEXPR void on_offset_year() {} + FMT_CONSTEXPR void on_century(numeric_system) {} + FMT_CONSTEXPR void on_iso_week_based_year() {} + FMT_CONSTEXPR void on_iso_week_based_short_year() {} + FMT_CONSTEXPR void on_abbr_weekday() {} + FMT_CONSTEXPR void on_full_weekday() {} + FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {} + FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {} + FMT_CONSTEXPR void on_abbr_month() {} + FMT_CONSTEXPR void on_full_month() {} + FMT_CONSTEXPR void on_dec_month(numeric_system) {} + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) {} + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) {} + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) {} + FMT_CONSTEXPR void on_day_of_year() {} + FMT_CONSTEXPR void on_day_of_month(numeric_system) {} + FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_datetime(numeric_system) {} + FMT_CONSTEXPR void on_loc_date(numeric_system) {} + FMT_CONSTEXPR void on_loc_time(numeric_system) {} + FMT_CONSTEXPR void on_us_date() {} + FMT_CONSTEXPR void on_iso_date() {} + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_utc_offset(numeric_system) {} + FMT_CONSTEXPR void on_tz_name() {} +}; + +inline auto tm_wday_full_name(int wday) -> const char* { + static constexpr const char* full_name_list[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"}; + return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?"; +} +inline auto tm_wday_short_name(int wday) -> const char* { + static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"}; + return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???"; +} + +inline auto tm_mon_full_name(int mon) -> const char* { + static constexpr const char* full_name_list[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; + return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?"; +} +inline auto tm_mon_short_name(int mon) -> const char* { + static constexpr const char* short_name_list[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???"; +} + +template +struct has_member_data_tm_gmtoff : std::false_type {}; +template +struct has_member_data_tm_gmtoff> + : std::true_type {}; + +template +struct has_member_data_tm_zone : std::false_type {}; +template +struct has_member_data_tm_zone> + : std::true_type {}; + +#if FMT_USE_TZSET +inline void tzset_once() { + static bool init = []() -> bool { + _tzset(); + return true; + }(); + ignore_unused(init); +} +#endif + +// Converts value to Int and checks that it's in the range [0, upper). +template ::value)> +inline auto to_nonnegative_int(T value, Int upper) -> Int { + if (!std::is_unsigned::value && + (value < 0 || to_unsigned(value) > to_unsigned(upper))) { + FMT_THROW(fmt::format_error("chrono value is out of range")); + } + return static_cast(value); +} +template ::value)> +inline auto to_nonnegative_int(T value, Int upper) -> Int { + if (value < 0 || value > static_cast(upper)) + FMT_THROW(format_error("invalid value")); + return static_cast(value); +} + +constexpr auto pow10(std::uint32_t n) -> long long { + return n == 0 ? 1 : 10 * pow10(n - 1); +} + +// Counts the number of fractional digits in the range [0, 18] according to the +// C++20 spec. If more than 18 fractional digits are required then returns 6 for +// microseconds precision. +template () / 10)> +struct count_fractional_digits { + static constexpr int value = + Num % Den == 0 ? N : count_fractional_digits::value; +}; + +// Base case that doesn't instantiate any more templates +// in order to avoid overflow. +template +struct count_fractional_digits { + static constexpr int value = (Num % Den == 0) ? N : 6; +}; + +// Format subseconds which are given as an integer type with an appropriate +// number of digits. +template +void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { + constexpr auto num_fractional_digits = + count_fractional_digits::value; + + using subsecond_precision = std::chrono::duration< + typename std::common_type::type, + std::ratio<1, detail::pow10(num_fractional_digits)>>; + + const auto fractional = d - fmt_duration_cast(d); + const auto subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? fractional.count() + : fmt_duration_cast(fractional).count(); + auto n = static_cast>(subseconds); + const int num_digits = detail::count_digits(n); + + int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits); + if (precision < 0) { + FMT_ASSERT(!std::is_floating_point::value, ""); + if (std::ratio_less::value) { + *out++ = '.'; + out = std::fill_n(out, leading_zeroes, '0'); + out = format_decimal(out, n, num_digits).end; + } + } else { + *out++ = '.'; + leading_zeroes = (std::min)(leading_zeroes, precision); + out = std::fill_n(out, leading_zeroes, '0'); + int remaining = precision - leading_zeroes; + if (remaining != 0 && remaining < num_digits) { + n /= to_unsigned(detail::pow10(to_unsigned(num_digits - remaining))); + out = format_decimal(out, n, remaining).end; + return; + } + out = format_decimal(out, n, num_digits).end; + remaining -= num_digits; + out = std::fill_n(out, remaining, '0'); + } +} + +// Format subseconds which are given as a floating point type with an +// appropriate number of digits. We cannot pass the Duration here, as we +// explicitly need to pass the Rep value in the chrono_formatter. +template +void write_floating_seconds(memory_buffer& buf, Duration duration, + int num_fractional_digits = -1) { + using rep = typename Duration::rep; + FMT_ASSERT(std::is_floating_point::value, ""); + + auto val = duration.count(); + + if (num_fractional_digits < 0) { + // For `std::round` with fallback to `round`: + // On some toolchains `std::round` is not available (e.g. GCC 6). + using namespace std; + num_fractional_digits = + count_fractional_digits::value; + if (num_fractional_digits < 6 && static_cast(round(val)) != val) + num_fractional_digits = 6; + } + + fmt::format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), + std::fmod(val * static_cast(Duration::period::num) / + static_cast(Duration::period::den), + static_cast(60)), + num_fractional_digits); +} + +template +class tm_writer { + private: + static constexpr int days_per_week = 7; + + const std::locale& loc_; + const bool is_classic_; + OutputIt out_; + const Duration* subsecs_; + const std::tm& tm_; + + auto tm_sec() const noexcept -> int { + FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, ""); + return tm_.tm_sec; + } + auto tm_min() const noexcept -> int { + FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, ""); + return tm_.tm_min; + } + auto tm_hour() const noexcept -> int { + FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, ""); + return tm_.tm_hour; + } + auto tm_mday() const noexcept -> int { + FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, ""); + return tm_.tm_mday; + } + auto tm_mon() const noexcept -> int { + FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, ""); + return tm_.tm_mon; + } + auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; } + auto tm_wday() const noexcept -> int { + FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, ""); + return tm_.tm_wday; + } + auto tm_yday() const noexcept -> int { + FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, ""); + return tm_.tm_yday; + } + + auto tm_hour12() const noexcept -> int { + const auto h = tm_hour(); + const auto z = h < 12 ? h : h - 12; + return z == 0 ? 12 : z; + } + + // POSIX and the C Standard are unclear or inconsistent about what %C and %y + // do if the year is negative or exceeds 9999. Use the convention that %C + // concatenated with %y yields the same output as %Y, and that %Y contains at + // least 4 characters, with more only if necessary. + auto split_year_lower(long long year) const noexcept -> int { + auto l = year % 100; + if (l < 0) l = -l; // l in [0, 99] + return static_cast(l); + } + + // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date. + auto iso_year_weeks(long long curr_year) const noexcept -> int { + const auto prev_year = curr_year - 1; + const auto curr_p = + (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % + days_per_week; + const auto prev_p = + (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % + days_per_week; + return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); + } + auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int { + return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) / + days_per_week; + } + auto tm_iso_week_year() const noexcept -> long long { + const auto year = tm_year(); + const auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return year - 1; + if (w > iso_year_weeks(year)) return year + 1; + return year; + } + auto tm_iso_week_of_year() const noexcept -> int { + const auto year = tm_year(); + const auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return iso_year_weeks(year - 1); + if (w > iso_year_weeks(year)) return 1; + return w; + } + + void write1(int value) { + *out_++ = static_cast('0' + to_unsigned(value) % 10); + } + void write2(int value) { + const char* d = digits2(to_unsigned(value) % 100); + *out_++ = *d++; + *out_++ = *d; + } + void write2(int value, pad_type pad) { + unsigned int v = to_unsigned(value) % 100; + if (v >= 10) { + const char* d = digits2(v); + *out_++ = *d++; + *out_++ = *d; + } else { + out_ = detail::write_padding(out_, pad); + *out_++ = static_cast('0' + v); + } + } + + void write_year_extended(long long year) { + // At least 4 characters. + int width = 4; + if (year < 0) { + *out_++ = '-'; + year = 0 - year; + --width; + } + uint32_or_64_or_128_t n = to_unsigned(year); + const int num_digits = count_digits(n); + if (width > num_digits) out_ = std::fill_n(out_, width - num_digits, '0'); + out_ = format_decimal(out_, n, num_digits).end; + } + void write_year(long long year) { + if (year >= 0 && year < 10000) { + write2(static_cast(year / 100)); + write2(static_cast(year % 100)); + } else { + write_year_extended(year); + } + } + + void write_utc_offset(long offset, numeric_system ns) { + if (offset < 0) { + *out_++ = '-'; + offset = -offset; + } else { + *out_++ = '+'; + } + offset /= 60; + write2(static_cast(offset / 60)); + if (ns != numeric_system::standard) *out_++ = ':'; + write2(static_cast(offset % 60)); + } + template ::value)> + void format_utc_offset_impl(const T& tm, numeric_system ns) { + write_utc_offset(tm.tm_gmtoff, ns); + } + template ::value)> + void format_utc_offset_impl(const T& tm, numeric_system ns) { +#if defined(_WIN32) && defined(_UCRT) +# if FMT_USE_TZSET + tzset_once(); +# endif + long offset = 0; + _get_timezone(&offset); + if (tm.tm_isdst) { + long dstbias = 0; + _get_dstbias(&dstbias); + offset += dstbias; + } + write_utc_offset(-offset, ns); +#else + if (ns == numeric_system::standard) return format_localized('z'); + + // Extract timezone offset from timezone conversion functions. + std::tm gtm = tm; + std::time_t gt = std::mktime(>m); + std::tm ltm = gmtime(gt); + std::time_t lt = std::mktime(<m); + long offset = gt - lt; + write_utc_offset(offset, ns); +#endif + } + + template ::value)> + void format_tz_name_impl(const T& tm) { + if (is_classic_) + out_ = write_tm_str(out_, tm.tm_zone, loc_); + else + format_localized('Z'); + } + template ::value)> + void format_tz_name_impl(const T&) { + format_localized('Z'); + } + + void format_localized(char format, char modifier = 0) { + out_ = write(out_, tm_, loc_, format, modifier); + } + + public: + tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm, + const Duration* subsecs = nullptr) + : loc_(loc), + is_classic_(loc_ == get_classic_locale()), + out_(out), + subsecs_(subsecs), + tm_(tm) {} + + auto out() const -> OutputIt { return out_; } + + FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { + out_ = copy_str(begin, end, out_); + } + + void on_abbr_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_short_name(tm_wday())); + else + format_localized('a'); + } + void on_full_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_full_name(tm_wday())); + else + format_localized('A'); + } + void on_dec0_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write1(tm_wday()); + format_localized('w', 'O'); + } + void on_dec1_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write1(wday == 0 ? days_per_week : wday); + } else { + format_localized('u', 'O'); + } + } + + void on_abbr_month() { + if (is_classic_) + out_ = write(out_, tm_mon_short_name(tm_mon())); + else + format_localized('b'); + } + void on_full_month() { + if (is_classic_) + out_ = write(out_, tm_mon_full_name(tm_mon())); + else + format_localized('B'); + } + + void on_datetime(numeric_system ns) { + if (is_classic_) { + on_abbr_weekday(); + *out_++ = ' '; + on_abbr_month(); + *out_++ = ' '; + on_day_of_month_space(numeric_system::standard); + *out_++ = ' '; + on_iso_time(); + *out_++ = ' '; + on_year(numeric_system::standard); + } else { + format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); + } + } + void on_loc_date(numeric_system ns) { + if (is_classic_) + on_us_date(); + else + format_localized('x', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_loc_time(numeric_system ns) { + if (is_classic_) + on_iso_time(); + else + format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_us_date() { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_mon() + 1), + to_unsigned(tm_mday()), + to_unsigned(split_year_lower(tm_year())), '/'); + out_ = copy_str(std::begin(buf), std::end(buf), out_); + } + void on_iso_date() { + auto year = tm_year(); + char buf[10]; + size_t offset = 0; + if (year >= 0 && year < 10000) { + copy2(buf, digits2(static_cast(year / 100))); + } else { + offset = 4; + write_year_extended(year); + year = 0; + } + write_digit2_separated(buf + 2, static_cast(year % 100), + to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()), + '-'); + out_ = copy_str(std::begin(buf) + offset, std::end(buf), out_); + } + + void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } + void on_tz_name() { format_tz_name_impl(tm_); } + + void on_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write_year(tm_year()); + format_localized('Y', 'E'); + } + void on_short_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(split_year_lower(tm_year())); + format_localized('y', 'O'); + } + void on_offset_year() { + if (is_classic_) return write2(split_year_lower(tm_year())); + format_localized('y', 'E'); + } + + void on_century(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto year = tm_year(); + auto upper = year / 100; + if (year >= -99 && year < 0) { + // Zero upper on negative year. + *out_++ = '-'; + *out_++ = '0'; + } else if (upper >= 0 && upper < 100) { + write2(static_cast(upper)); + } else { + out_ = write(out_, upper); + } + } else { + format_localized('C', 'E'); + } + } + + void on_dec_month(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mon() + 1); + format_localized('m', 'O'); + } + + void on_dec0_week_of_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week); + format_localized('U', 'O'); + } + void on_dec1_week_of_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write2((tm_yday() + days_per_week - + (wday == 0 ? (days_per_week - 1) : (wday - 1))) / + days_per_week); + } else { + format_localized('W', 'O'); + } + } + void on_iso_week_of_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_iso_week_of_year()); + format_localized('V', 'O'); + } + + void on_iso_week_based_year() { write_year(tm_iso_week_year()); } + void on_iso_week_based_short_year() { + write2(split_year_lower(tm_iso_week_year())); + } + + void on_day_of_year() { + auto yday = tm_yday() + 1; + write1(yday / 100); + write2(yday % 100); + } + void on_day_of_month(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write2(tm_mday()); + format_localized('d', 'O'); + } + void on_day_of_month_space(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto mday = to_unsigned(tm_mday()) % 100; + const char* d2 = digits2(mday); + *out_++ = mday < 10 ? ' ' : d2[0]; + *out_++ = d2[1]; + } else { + format_localized('e', 'O'); + } + } + + void on_24_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour(), pad); + format_localized('H', 'O'); + } + void on_12_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour12(), pad); + format_localized('I', 'O'); + } + void on_minute(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_min(), pad); + format_localized('M', 'O'); + } + + void on_second(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + write2(tm_sec(), pad); + if (subsecs_) { + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, *subsecs_); + if (buf.size() > 1) { + // Remove the leading "0", write something like ".123". + out_ = std::copy(buf.begin() + 1, buf.end(), out_); + } + } else { + write_fractional_seconds(out_, *subsecs_); + } + } + } else { + // Currently no formatting of subseconds when a locale is set. + format_localized('S', 'O'); + } + } + + void on_12_hour_time() { + if (is_classic_) { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_hour12()), + to_unsigned(tm_min()), to_unsigned(tm_sec()), ':'); + out_ = copy_str(std::begin(buf), std::end(buf), out_); + *out_++ = ' '; + on_am_pm(); + } else { + format_localized('r'); + } + } + void on_24_hour_time() { + write2(tm_hour()); + *out_++ = ':'; + write2(tm_min()); + } + void on_iso_time() { + on_24_hour_time(); + *out_++ = ':'; + on_second(numeric_system::standard, pad_type::unspecified); + } + + void on_am_pm() { + if (is_classic_) { + *out_++ = tm_hour() < 12 ? 'A' : 'P'; + *out_++ = 'M'; + } else { + format_localized('p'); + } + } + + // These apply to chrono durations but not tm. + void on_duration_value() {} + void on_duration_unit() {} +}; + +struct chrono_format_checker : null_chrono_spec_handler { + bool has_precision_integral = false; + + FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_day_of_year() {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_duration_value() const { + if (has_precision_integral) { + FMT_THROW(format_error("precision not allowed for this argument type")); + } + } + FMT_CONSTEXPR void on_duration_unit() {} +}; + +template ::value&& has_isfinite::value)> +inline auto isfinite(T) -> bool { + return true; +} + +template ::value)> +inline auto mod(T x, int y) -> T { + return x % static_cast(y); +} +template ::value)> +inline auto mod(T x, int y) -> T { + return std::fmod(x, static_cast(y)); +} + +// If T is an integral type, maps T to its unsigned counterpart, otherwise +// leaves it unchanged (unlike std::make_unsigned). +template ::value> +struct make_unsigned_or_unchanged { + using type = T; +}; + +template struct make_unsigned_or_unchanged { + using type = typename std::make_unsigned::type; +}; + +template ::value)> +inline auto get_milliseconds(std::chrono::duration d) + -> std::chrono::duration { + // this may overflow and/or the result may not fit in the + // target type. +#if FMT_SAFE_DURATION_CAST + using CommonSecondsType = + typename std::common_type::type; + const auto d_as_common = fmt_duration_cast(d); + const auto d_as_whole_seconds = + fmt_duration_cast(d_as_common); + // this conversion should be nonproblematic + const auto diff = d_as_common - d_as_whole_seconds; + const auto ms = + fmt_duration_cast>(diff); + return ms; +#else + auto s = fmt_duration_cast(d); + return fmt_duration_cast(d - s); +#endif +} + +template ::value)> +auto format_duration_value(OutputIt out, Rep val, int) -> OutputIt { + return write(out, val); +} + +template ::value)> +auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt { + auto specs = format_specs(); + specs.precision = precision; + specs.type = precision >= 0 ? presentation_type::fixed_lower + : presentation_type::general_lower; + return write(out, val, specs); +} + +template +auto copy_unit(string_view unit, OutputIt out, Char) -> OutputIt { + return std::copy(unit.begin(), unit.end(), out); +} + +template +auto copy_unit(string_view unit, OutputIt out, wchar_t) -> OutputIt { + // This works when wchar_t is UTF-32 because units only contain characters + // that have the same representation in UTF-16 and UTF-32. + utf8_to_utf16 u(unit); + return std::copy(u.c_str(), u.c_str() + u.size(), out); +} + +template +auto format_duration_unit(OutputIt out) -> OutputIt { + if (const char* unit = get_units()) + return copy_unit(string_view(unit), out, Char()); + *out++ = '['; + out = write(out, Period::num); + if (const_check(Period::den != 1)) { + *out++ = '/'; + out = write(out, Period::den); + } + *out++ = ']'; + *out++ = 's'; + return out; +} + +class get_locale { + private: + union { + std::locale locale_; + }; + bool has_locale_ = false; + + public: + get_locale(bool localized, locale_ref loc) : has_locale_(localized) { + if (localized) + ::new (&locale_) std::locale(loc.template get()); + } + ~get_locale() { + if (has_locale_) locale_.~locale(); + } + operator const std::locale&() const { + return has_locale_ ? locale_ : get_classic_locale(); + } +}; + +template +struct chrono_formatter { + FormatContext& context; + OutputIt out; + int precision; + bool localized = false; + // rep is unsigned to avoid overflow. + using rep = + conditional_t::value && sizeof(Rep) < sizeof(int), + unsigned, typename make_unsigned_or_unchanged::type>; + rep val; + using seconds = std::chrono::duration; + seconds s; + using milliseconds = std::chrono::duration; + bool negative; + + using char_type = typename FormatContext::char_type; + using tm_writer_type = tm_writer; + + chrono_formatter(FormatContext& ctx, OutputIt o, + std::chrono::duration d) + : context(ctx), + out(o), + val(static_cast(d.count())), + negative(false) { + if (d.count() < 0) { + val = 0 - val; + negative = true; + } + + // this may overflow and/or the result may not fit in the + // target type. + // might need checked conversion (rep!=Rep) + s = fmt_duration_cast(std::chrono::duration(val)); + } + + // returns true if nan or inf, writes to out. + auto handle_nan_inf() -> bool { + if (isfinite(val)) { + return false; + } + if (isnan(val)) { + write_nan(); + return true; + } + // must be +-inf + if (val > 0) { + write_pinf(); + } else { + write_ninf(); + } + return true; + } + + auto days() const -> Rep { return static_cast(s.count() / 86400); } + auto hour() const -> Rep { + return static_cast(mod((s.count() / 3600), 24)); + } + + auto hour12() const -> Rep { + Rep hour = static_cast(mod((s.count() / 3600), 12)); + return hour <= 0 ? 12 : hour; + } + + auto minute() const -> Rep { + return static_cast(mod((s.count() / 60), 60)); + } + auto second() const -> Rep { return static_cast(mod(s.count(), 60)); } + + auto time() const -> std::tm { + auto time = std::tm(); + time.tm_hour = to_nonnegative_int(hour(), 24); + time.tm_min = to_nonnegative_int(minute(), 60); + time.tm_sec = to_nonnegative_int(second(), 60); + return time; + } + + void write_sign() { + if (negative) { + *out++ = '-'; + negative = false; + } + } + + void write(Rep value, int width, pad_type pad = pad_type::unspecified) { + write_sign(); + if (isnan(value)) return write_nan(); + uint32_or_64_or_128_t n = + to_unsigned(to_nonnegative_int(value, max_value())); + int num_digits = detail::count_digits(n); + if (width > num_digits) { + out = detail::write_padding(out, pad, width - num_digits); + } + out = format_decimal(out, n, num_digits).end; + } + + void write_nan() { std::copy_n("nan", 3, out); } + void write_pinf() { std::copy_n("inf", 3, out); } + void write_ninf() { std::copy_n("-inf", 4, out); } + + template + void format_tm(const tm& time, Callback cb, Args... args) { + if (isnan(val)) return write_nan(); + get_locale loc(localized, context.locale()); + auto w = tm_writer_type(loc, out, time); + (w.*cb)(args...); + out = w.out(); + } + + void on_text(const char_type* begin, const char_type* end) { + std::copy(begin, end, out); + } + + // These are not implemented because durations don't have date information. + void on_abbr_weekday() {} + void on_full_weekday() {} + void on_dec0_weekday(numeric_system) {} + void on_dec1_weekday(numeric_system) {} + void on_abbr_month() {} + void on_full_month() {} + void on_datetime(numeric_system) {} + void on_loc_date(numeric_system) {} + void on_loc_time(numeric_system) {} + void on_us_date() {} + void on_iso_date() {} + void on_utc_offset(numeric_system) {} + void on_tz_name() {} + void on_year(numeric_system) {} + void on_short_year(numeric_system) {} + void on_offset_year() {} + void on_century(numeric_system) {} + void on_iso_week_based_year() {} + void on_iso_week_based_short_year() {} + void on_dec_month(numeric_system) {} + void on_dec0_week_of_year(numeric_system) {} + void on_dec1_week_of_year(numeric_system) {} + void on_iso_week_of_year(numeric_system) {} + void on_day_of_month(numeric_system) {} + void on_day_of_month_space(numeric_system) {} + + void on_day_of_year() { + if (handle_nan_inf()) return; + write(days(), 0); + } + + void on_24_hour(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(hour(), 2, pad); + auto time = tm(); + time.tm_hour = to_nonnegative_int(hour(), 24); + format_tm(time, &tm_writer_type::on_24_hour, ns, pad); + } + + void on_12_hour(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(hour12(), 2, pad); + auto time = tm(); + time.tm_hour = to_nonnegative_int(hour12(), 12); + format_tm(time, &tm_writer_type::on_12_hour, ns, pad); + } + + void on_minute(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(minute(), 2, pad); + auto time = tm(); + time.tm_min = to_nonnegative_int(minute(), 60); + format_tm(time, &tm_writer_type::on_minute, ns, pad); + } + + void on_second(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) { + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, std::chrono::duration(val), + precision); + if (negative) *out++ = '-'; + if (buf.size() < 2 || buf[1] == '.') { + out = detail::write_padding(out, pad); + } + out = std::copy(buf.begin(), buf.end(), out); + } else { + write(second(), 2, pad); + write_fractional_seconds( + out, std::chrono::duration(val), precision); + } + return; + } + auto time = tm(); + time.tm_sec = to_nonnegative_int(second(), 60); + format_tm(time, &tm_writer_type::on_second, ns, pad); + } + + void on_12_hour_time() { + if (handle_nan_inf()) return; + format_tm(time(), &tm_writer_type::on_12_hour_time); + } + + void on_24_hour_time() { + if (handle_nan_inf()) { + *out++ = ':'; + handle_nan_inf(); + return; + } + + write(hour(), 2); + *out++ = ':'; + write(minute(), 2); + } + + void on_iso_time() { + on_24_hour_time(); + *out++ = ':'; + if (handle_nan_inf()) return; + on_second(numeric_system::standard, pad_type::unspecified); + } + + void on_am_pm() { + if (handle_nan_inf()) return; + format_tm(time(), &tm_writer_type::on_am_pm); + } + + void on_duration_value() { + if (handle_nan_inf()) return; + write_sign(); + out = format_duration_value(out, val, precision); + } + + void on_duration_unit() { + out = format_duration_unit(out); + } +}; + +} // namespace detail + +#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907 +using weekday = std::chrono::weekday; +#else +// A fallback version of weekday. +class weekday { + private: + unsigned char value; + + public: + weekday() = default; + explicit constexpr weekday(unsigned wd) noexcept + : value(static_cast(wd != 7 ? wd : 0)) {} + constexpr auto c_encoding() const noexcept -> unsigned { return value; } +}; + +class year_month_day {}; +#endif + +// A rudimentary weekday formatter. +template struct formatter { + private: + bool localized = false; + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto begin = ctx.begin(), end = ctx.end(); + if (begin != end && *begin == 'L') { + ++begin; + localized = true; + } + return begin; + } + + template + auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_wday = static_cast(wd.c_encoding()); + detail::get_locale loc(localized, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_weekday(); + return w.out(); + } +}; + +template +struct formatter, Char> { + private: + format_specs specs_; + detail::arg_ref width_ref_; + detail::arg_ref precision_ref_; + bool localized_ = false; + basic_string_view format_str_; + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); + if (it == end) return it; + + auto checker = detail::chrono_format_checker(); + if (*it == '.') { + checker.has_precision_integral = !std::is_floating_point::value; + it = detail::parse_precision(it, end, specs_.precision, precision_ref_, + ctx); + } + if (it != end && *it == 'L') { + localized_ = true; + ++it; + } + end = detail::parse_chrono_format(it, end, checker); + format_str_ = {it, detail::to_unsigned(end - it)}; + return end; + } + + template + auto format(std::chrono::duration d, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs = specs_; + auto precision = specs.precision; + specs.precision = -1; + auto begin = format_str_.begin(), end = format_str_.end(); + // As a possible future optimization, we could avoid extra copying if width + // is not specified. + auto buf = basic_memory_buffer(); + auto out = std::back_inserter(buf); + detail::handle_dynamic_spec(specs.width, width_ref_, + ctx); + detail::handle_dynamic_spec(precision, + precision_ref_, ctx); + if (begin == end || *begin == '}') { + out = detail::format_duration_value(out, d.count(), precision); + detail::format_duration_unit(out); + } else { + using chrono_formatter = + detail::chrono_formatter; + auto f = chrono_formatter(ctx, out, d); + f.precision = precision; + f.localized = localized_; + detail::parse_chrono_format(begin, end, f); + } + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } +}; + +template +struct formatter, + Char> : formatter { + FMT_CONSTEXPR formatter() { + this->format_str_ = detail::string_literal{}; + } + + template + auto format(std::chrono::time_point val, + FormatContext& ctx) const -> decltype(ctx.out()) { + using period = typename Duration::period; + if (detail::const_check( + period::num != 1 || period::den != 1 || + std::is_floating_point::value)) { + const auto epoch = val.time_since_epoch(); + auto subsecs = detail::fmt_duration_cast( + epoch - detail::fmt_duration_cast(epoch)); + + if (subsecs.count() < 0) { + auto second = + detail::fmt_duration_cast(std::chrono::seconds(1)); + if (epoch.count() < ((Duration::min)() + second).count()) + FMT_THROW(format_error("duration is too small")); + subsecs += second; + val -= second; + } + + return formatter::do_format(gmtime(val), ctx, &subsecs); + } + + return formatter::format(gmtime(val), ctx); + } +}; + +#if FMT_USE_LOCAL_TIME +template +struct formatter, Char> + : formatter { + FMT_CONSTEXPR formatter() { + this->format_str_ = detail::string_literal{}; + } + + template + auto format(std::chrono::local_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + using period = typename Duration::period; + if (period::num != 1 || period::den != 1 || + std::is_floating_point::value) { + const auto epoch = val.time_since_epoch(); + const auto subsecs = detail::fmt_duration_cast( + epoch - detail::fmt_duration_cast(epoch)); + + return formatter::do_format(localtime(val), ctx, &subsecs); + } + + return formatter::format(localtime(val), ctx); + } +}; +#endif + +#if FMT_USE_UTC_TIME +template +struct formatter, + Char> + : formatter, + Char> { + template + auto format(std::chrono::time_point val, + FormatContext& ctx) const -> decltype(ctx.out()) { + return formatter< + std::chrono::time_point, + Char>::format(std::chrono::utc_clock::to_sys(val), ctx); + } +}; +#endif + +template struct formatter { + private: + format_specs specs_; + detail::arg_ref width_ref_; + + protected: + basic_string_view format_str_; + + template + auto do_format(const std::tm& tm, FormatContext& ctx, + const Duration* subsecs) const -> decltype(ctx.out()) { + auto specs = specs_; + auto buf = basic_memory_buffer(); + auto out = std::back_inserter(buf); + detail::handle_dynamic_spec(specs.width, width_ref_, + ctx); + + auto loc_ref = ctx.locale(); + detail::get_locale loc(static_cast(loc_ref), loc_ref); + auto w = + detail::tm_writer(loc, out, tm, subsecs); + detail::parse_chrono_format(format_str_.begin(), format_str_.end(), w); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); + if (it == end) return it; + + end = detail::parse_chrono_format(it, end, detail::tm_format_checker()); + // Replace the default format_str only if the new spec is not empty. + if (end != it) format_str_ = {it, detail::to_unsigned(end - it)}; + return end; + } + + template + auto format(const std::tm& tm, FormatContext& ctx) const + -> decltype(ctx.out()) { + return do_format(tm, ctx, nullptr); + } +}; + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_CHRONO_H_ diff --git a/thirdparty/fmt/include/fmt/color.h b/thirdparty/fmt/include/fmt/color.h new file mode 100644 index 000000000..367849a86 --- /dev/null +++ b/thirdparty/fmt/include/fmt/color.h @@ -0,0 +1,643 @@ +// Formatting library for C++ - color support +// +// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_COLOR_H_ +#define FMT_COLOR_H_ + +#include "format.h" + +FMT_BEGIN_NAMESPACE +FMT_BEGIN_EXPORT + +enum class color : uint32_t { + alice_blue = 0xF0F8FF, // rgb(240,248,255) + antique_white = 0xFAEBD7, // rgb(250,235,215) + aqua = 0x00FFFF, // rgb(0,255,255) + aquamarine = 0x7FFFD4, // rgb(127,255,212) + azure = 0xF0FFFF, // rgb(240,255,255) + beige = 0xF5F5DC, // rgb(245,245,220) + bisque = 0xFFE4C4, // rgb(255,228,196) + black = 0x000000, // rgb(0,0,0) + blanched_almond = 0xFFEBCD, // rgb(255,235,205) + blue = 0x0000FF, // rgb(0,0,255) + blue_violet = 0x8A2BE2, // rgb(138,43,226) + brown = 0xA52A2A, // rgb(165,42,42) + burly_wood = 0xDEB887, // rgb(222,184,135) + cadet_blue = 0x5F9EA0, // rgb(95,158,160) + chartreuse = 0x7FFF00, // rgb(127,255,0) + chocolate = 0xD2691E, // rgb(210,105,30) + coral = 0xFF7F50, // rgb(255,127,80) + cornflower_blue = 0x6495ED, // rgb(100,149,237) + cornsilk = 0xFFF8DC, // rgb(255,248,220) + crimson = 0xDC143C, // rgb(220,20,60) + cyan = 0x00FFFF, // rgb(0,255,255) + dark_blue = 0x00008B, // rgb(0,0,139) + dark_cyan = 0x008B8B, // rgb(0,139,139) + dark_golden_rod = 0xB8860B, // rgb(184,134,11) + dark_gray = 0xA9A9A9, // rgb(169,169,169) + dark_green = 0x006400, // rgb(0,100,0) + dark_khaki = 0xBDB76B, // rgb(189,183,107) + dark_magenta = 0x8B008B, // rgb(139,0,139) + dark_olive_green = 0x556B2F, // rgb(85,107,47) + dark_orange = 0xFF8C00, // rgb(255,140,0) + dark_orchid = 0x9932CC, // rgb(153,50,204) + dark_red = 0x8B0000, // rgb(139,0,0) + dark_salmon = 0xE9967A, // rgb(233,150,122) + dark_sea_green = 0x8FBC8F, // rgb(143,188,143) + dark_slate_blue = 0x483D8B, // rgb(72,61,139) + dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) + dark_turquoise = 0x00CED1, // rgb(0,206,209) + dark_violet = 0x9400D3, // rgb(148,0,211) + deep_pink = 0xFF1493, // rgb(255,20,147) + deep_sky_blue = 0x00BFFF, // rgb(0,191,255) + dim_gray = 0x696969, // rgb(105,105,105) + dodger_blue = 0x1E90FF, // rgb(30,144,255) + fire_brick = 0xB22222, // rgb(178,34,34) + floral_white = 0xFFFAF0, // rgb(255,250,240) + forest_green = 0x228B22, // rgb(34,139,34) + fuchsia = 0xFF00FF, // rgb(255,0,255) + gainsboro = 0xDCDCDC, // rgb(220,220,220) + ghost_white = 0xF8F8FF, // rgb(248,248,255) + gold = 0xFFD700, // rgb(255,215,0) + golden_rod = 0xDAA520, // rgb(218,165,32) + gray = 0x808080, // rgb(128,128,128) + green = 0x008000, // rgb(0,128,0) + green_yellow = 0xADFF2F, // rgb(173,255,47) + honey_dew = 0xF0FFF0, // rgb(240,255,240) + hot_pink = 0xFF69B4, // rgb(255,105,180) + indian_red = 0xCD5C5C, // rgb(205,92,92) + indigo = 0x4B0082, // rgb(75,0,130) + ivory = 0xFFFFF0, // rgb(255,255,240) + khaki = 0xF0E68C, // rgb(240,230,140) + lavender = 0xE6E6FA, // rgb(230,230,250) + lavender_blush = 0xFFF0F5, // rgb(255,240,245) + lawn_green = 0x7CFC00, // rgb(124,252,0) + lemon_chiffon = 0xFFFACD, // rgb(255,250,205) + light_blue = 0xADD8E6, // rgb(173,216,230) + light_coral = 0xF08080, // rgb(240,128,128) + light_cyan = 0xE0FFFF, // rgb(224,255,255) + light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) + light_gray = 0xD3D3D3, // rgb(211,211,211) + light_green = 0x90EE90, // rgb(144,238,144) + light_pink = 0xFFB6C1, // rgb(255,182,193) + light_salmon = 0xFFA07A, // rgb(255,160,122) + light_sea_green = 0x20B2AA, // rgb(32,178,170) + light_sky_blue = 0x87CEFA, // rgb(135,206,250) + light_slate_gray = 0x778899, // rgb(119,136,153) + light_steel_blue = 0xB0C4DE, // rgb(176,196,222) + light_yellow = 0xFFFFE0, // rgb(255,255,224) + lime = 0x00FF00, // rgb(0,255,0) + lime_green = 0x32CD32, // rgb(50,205,50) + linen = 0xFAF0E6, // rgb(250,240,230) + magenta = 0xFF00FF, // rgb(255,0,255) + maroon = 0x800000, // rgb(128,0,0) + medium_aquamarine = 0x66CDAA, // rgb(102,205,170) + medium_blue = 0x0000CD, // rgb(0,0,205) + medium_orchid = 0xBA55D3, // rgb(186,85,211) + medium_purple = 0x9370DB, // rgb(147,112,219) + medium_sea_green = 0x3CB371, // rgb(60,179,113) + medium_slate_blue = 0x7B68EE, // rgb(123,104,238) + medium_spring_green = 0x00FA9A, // rgb(0,250,154) + medium_turquoise = 0x48D1CC, // rgb(72,209,204) + medium_violet_red = 0xC71585, // rgb(199,21,133) + midnight_blue = 0x191970, // rgb(25,25,112) + mint_cream = 0xF5FFFA, // rgb(245,255,250) + misty_rose = 0xFFE4E1, // rgb(255,228,225) + moccasin = 0xFFE4B5, // rgb(255,228,181) + navajo_white = 0xFFDEAD, // rgb(255,222,173) + navy = 0x000080, // rgb(0,0,128) + old_lace = 0xFDF5E6, // rgb(253,245,230) + olive = 0x808000, // rgb(128,128,0) + olive_drab = 0x6B8E23, // rgb(107,142,35) + orange = 0xFFA500, // rgb(255,165,0) + orange_red = 0xFF4500, // rgb(255,69,0) + orchid = 0xDA70D6, // rgb(218,112,214) + pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) + pale_green = 0x98FB98, // rgb(152,251,152) + pale_turquoise = 0xAFEEEE, // rgb(175,238,238) + pale_violet_red = 0xDB7093, // rgb(219,112,147) + papaya_whip = 0xFFEFD5, // rgb(255,239,213) + peach_puff = 0xFFDAB9, // rgb(255,218,185) + peru = 0xCD853F, // rgb(205,133,63) + pink = 0xFFC0CB, // rgb(255,192,203) + plum = 0xDDA0DD, // rgb(221,160,221) + powder_blue = 0xB0E0E6, // rgb(176,224,230) + purple = 0x800080, // rgb(128,0,128) + rebecca_purple = 0x663399, // rgb(102,51,153) + red = 0xFF0000, // rgb(255,0,0) + rosy_brown = 0xBC8F8F, // rgb(188,143,143) + royal_blue = 0x4169E1, // rgb(65,105,225) + saddle_brown = 0x8B4513, // rgb(139,69,19) + salmon = 0xFA8072, // rgb(250,128,114) + sandy_brown = 0xF4A460, // rgb(244,164,96) + sea_green = 0x2E8B57, // rgb(46,139,87) + sea_shell = 0xFFF5EE, // rgb(255,245,238) + sienna = 0xA0522D, // rgb(160,82,45) + silver = 0xC0C0C0, // rgb(192,192,192) + sky_blue = 0x87CEEB, // rgb(135,206,235) + slate_blue = 0x6A5ACD, // rgb(106,90,205) + slate_gray = 0x708090, // rgb(112,128,144) + snow = 0xFFFAFA, // rgb(255,250,250) + spring_green = 0x00FF7F, // rgb(0,255,127) + steel_blue = 0x4682B4, // rgb(70,130,180) + tan = 0xD2B48C, // rgb(210,180,140) + teal = 0x008080, // rgb(0,128,128) + thistle = 0xD8BFD8, // rgb(216,191,216) + tomato = 0xFF6347, // rgb(255,99,71) + turquoise = 0x40E0D0, // rgb(64,224,208) + violet = 0xEE82EE, // rgb(238,130,238) + wheat = 0xF5DEB3, // rgb(245,222,179) + white = 0xFFFFFF, // rgb(255,255,255) + white_smoke = 0xF5F5F5, // rgb(245,245,245) + yellow = 0xFFFF00, // rgb(255,255,0) + yellow_green = 0x9ACD32 // rgb(154,205,50) +}; // enum class color + +enum class terminal_color : uint8_t { + black = 30, + red, + green, + yellow, + blue, + magenta, + cyan, + white, + bright_black = 90, + bright_red, + bright_green, + bright_yellow, + bright_blue, + bright_magenta, + bright_cyan, + bright_white +}; + +enum class emphasis : uint8_t { + bold = 1, + faint = 1 << 1, + italic = 1 << 2, + underline = 1 << 3, + blink = 1 << 4, + reverse = 1 << 5, + conceal = 1 << 6, + strikethrough = 1 << 7, +}; + +// rgb is a struct for red, green and blue colors. +// Using the name "rgb" makes some editors show the color in a tooltip. +struct rgb { + FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} + FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} + FMT_CONSTEXPR rgb(uint32_t hex) + : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} + FMT_CONSTEXPR rgb(color hex) + : r((uint32_t(hex) >> 16) & 0xFF), + g((uint32_t(hex) >> 8) & 0xFF), + b(uint32_t(hex) & 0xFF) {} + uint8_t r; + uint8_t g; + uint8_t b; +}; + +namespace detail { + +// color is a struct of either a rgb color or a terminal color. +struct color_type { + FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {} + FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} { + value.rgb_color = static_cast(rgb_color); + } + FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} { + value.rgb_color = (static_cast(rgb_color.r) << 16) | + (static_cast(rgb_color.g) << 8) | rgb_color.b; + } + FMT_CONSTEXPR color_type(terminal_color term_color) noexcept + : is_rgb(), value{} { + value.term_color = static_cast(term_color); + } + bool is_rgb; + union color_union { + uint8_t term_color; + uint32_t rgb_color; + } value; +}; +} // namespace detail + +/** A text style consisting of foreground and background colors and emphasis. */ +class text_style { + public: + FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept + : set_foreground_color(), set_background_color(), ems(em) {} + + FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { + if (!set_foreground_color) { + set_foreground_color = rhs.set_foreground_color; + foreground_color = rhs.foreground_color; + } else if (rhs.set_foreground_color) { + if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) + FMT_THROW(format_error("can't OR a terminal color")); + foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; + } + + if (!set_background_color) { + set_background_color = rhs.set_background_color; + background_color = rhs.background_color; + } else if (rhs.set_background_color) { + if (!background_color.is_rgb || !rhs.background_color.is_rgb) + FMT_THROW(format_error("can't OR a terminal color")); + background_color.value.rgb_color |= rhs.background_color.value.rgb_color; + } + + ems = static_cast(static_cast(ems) | + static_cast(rhs.ems)); + return *this; + } + + friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) + -> text_style { + return lhs |= rhs; + } + + FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { + return set_foreground_color; + } + FMT_CONSTEXPR auto has_background() const noexcept -> bool { + return set_background_color; + } + FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { + return static_cast(ems) != 0; + } + FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { + FMT_ASSERT(has_foreground(), "no foreground specified for this style"); + return foreground_color; + } + FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { + FMT_ASSERT(has_background(), "no background specified for this style"); + return background_color; + } + FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { + FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); + return ems; + } + + private: + FMT_CONSTEXPR text_style(bool is_foreground, + detail::color_type text_color) noexcept + : set_foreground_color(), set_background_color(), ems() { + if (is_foreground) { + foreground_color = text_color; + set_foreground_color = true; + } else { + background_color = text_color; + set_background_color = true; + } + } + + friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept + -> text_style; + + friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept + -> text_style; + + detail::color_type foreground_color; + detail::color_type background_color; + bool set_foreground_color; + bool set_background_color; + emphasis ems; +}; + +/** Creates a text style from the foreground (text) color. */ +FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept + -> text_style { + return text_style(true, foreground); +} + +/** Creates a text style from the background color. */ +FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept + -> text_style { + return text_style(false, background); +} + +FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept + -> text_style { + return text_style(lhs) | rhs; +} + +namespace detail { + +template struct ansi_color_escape { + FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, + const char* esc) noexcept { + // If we have a terminal color, we need to output another escape code + // sequence. + if (!text_color.is_rgb) { + bool is_background = esc == string_view("\x1b[48;2;"); + uint32_t value = text_color.value.term_color; + // Background ASCII codes are the same as the foreground ones but with + // 10 more. + if (is_background) value += 10u; + + size_t index = 0; + buffer[index++] = static_cast('\x1b'); + buffer[index++] = static_cast('['); + + if (value >= 100u) { + buffer[index++] = static_cast('1'); + value %= 100u; + } + buffer[index++] = static_cast('0' + value / 10u); + buffer[index++] = static_cast('0' + value % 10u); + + buffer[index++] = static_cast('m'); + buffer[index++] = static_cast('\0'); + return; + } + + for (int i = 0; i < 7; i++) { + buffer[i] = static_cast(esc[i]); + } + rgb color(text_color.value.rgb_color); + to_esc(color.r, buffer + 7, ';'); + to_esc(color.g, buffer + 11, ';'); + to_esc(color.b, buffer + 15, 'm'); + buffer[19] = static_cast(0); + } + FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { + uint8_t em_codes[num_emphases] = {}; + if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1; + if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2; + if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3; + if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4; + if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5; + if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7; + if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; + if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; + + size_t index = 0; + for (size_t i = 0; i < num_emphases; ++i) { + if (!em_codes[i]) continue; + buffer[index++] = static_cast('\x1b'); + buffer[index++] = static_cast('['); + buffer[index++] = static_cast('0' + em_codes[i]); + buffer[index++] = static_cast('m'); + } + buffer[index++] = static_cast(0); + } + FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } + + FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } + FMT_CONSTEXPR_CHAR_TRAITS auto end() const noexcept -> const Char* { + return buffer + std::char_traits::length(buffer); + } + + private: + static constexpr size_t num_emphases = 8; + Char buffer[7u + 3u * num_emphases + 1u]; + + static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, + char delimiter) noexcept { + out[0] = static_cast('0' + c / 100); + out[1] = static_cast('0' + c / 10 % 10); + out[2] = static_cast('0' + c % 10); + out[3] = static_cast(delimiter); + } + static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept + -> bool { + return static_cast(em) & static_cast(mask); + } +}; + +template +FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept + -> ansi_color_escape { + return ansi_color_escape(foreground, "\x1b[38;2;"); +} + +template +FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept + -> ansi_color_escape { + return ansi_color_escape(background, "\x1b[48;2;"); +} + +template +FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept + -> ansi_color_escape { + return ansi_color_escape(em); +} + +template inline void reset_color(buffer& buffer) { + auto reset_color = string_view("\x1b[0m"); + buffer.append(reset_color.begin(), reset_color.end()); +} + +template struct styled_arg : detail::view { + const T& value; + text_style style; + styled_arg(const T& v, text_style s) : value(v), style(s) {} +}; + +template +void vformat_to(buffer& buf, const text_style& ts, + basic_string_view format_str, + basic_format_args>> args) { + bool has_style = false; + if (ts.has_emphasis()) { + has_style = true; + auto emphasis = detail::make_emphasis(ts.get_emphasis()); + buf.append(emphasis.begin(), emphasis.end()); + } + if (ts.has_foreground()) { + has_style = true; + auto foreground = detail::make_foreground_color(ts.get_foreground()); + buf.append(foreground.begin(), foreground.end()); + } + if (ts.has_background()) { + has_style = true; + auto background = detail::make_background_color(ts.get_background()); + buf.append(background.begin(), background.end()); + } + detail::vformat_to(buf, format_str, args, {}); + if (has_style) detail::reset_color(buf); +} + +} // namespace detail + +inline void vprint(std::FILE* f, const text_style& ts, string_view fmt, + format_args args) { + // Legacy wide streams are not supported. + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + if (detail::is_utf8()) { + detail::print(f, string_view(buf.begin(), buf.size())); + return; + } + buf.push_back('\0'); + int result = std::fputs(buf.data(), f); + if (result < 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); +} + +/** + \rst + Formats a string and prints it to the specified file stream using ANSI + escape sequences to specify text formatting. + + **Example**:: + + fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + "Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +template ::value)> +void print(std::FILE* f, const text_style& ts, const S& format_str, + const Args&... args) { + vprint(f, ts, format_str, + fmt::make_format_args>>(args...)); +} + +/** + \rst + Formats a string and prints it to stdout using ANSI escape sequences to + specify text formatting. + + **Example**:: + + fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + "Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +template ::value)> +void print(const text_style& ts, const S& format_str, const Args&... args) { + return print(stdout, ts, format_str, args...); +} + +template > +inline auto vformat( + const text_style& ts, const S& format_str, + basic_format_args>> args) + -> std::basic_string { + basic_memory_buffer buf; + detail::vformat_to(buf, ts, detail::to_string_view(format_str), args); + return fmt::to_string(buf); +} + +/** + \rst + Formats arguments and returns the result as a string using ANSI + escape sequences to specify text formatting. + + **Example**:: + + #include + std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), + "The answer is {}", 42); + \endrst +*/ +template > +inline auto format(const text_style& ts, const S& format_str, + const Args&... args) -> std::basic_string { + return fmt::vformat(ts, detail::to_string_view(format_str), + fmt::make_format_args>(args...)); +} + +/** + Formats a string with the given text_style and writes the output to ``out``. + */ +template ::value)> +auto vformat_to(OutputIt out, const text_style& ts, + basic_string_view format_str, + basic_format_args>> args) + -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, ts, format_str, args); + return detail::get_iterator(buf, out); +} + +/** + \rst + Formats arguments with the given text_style, writes the result to the output + iterator ``out`` and returns the iterator past the end of the output range. + + **Example**:: + + std::vector out; + fmt::format_to(std::back_inserter(out), + fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); + \endrst +*/ +template < + typename OutputIt, typename S, typename... Args, + bool enable = detail::is_output_iterator>::value && + detail::is_string::value> +inline auto format_to(OutputIt out, const text_style& ts, const S& format_str, + Args&&... args) -> + typename std::enable_if::type { + return vformat_to(out, ts, detail::to_string_view(format_str), + fmt::make_format_args>>(args...)); +} + +template +struct formatter, Char> : formatter { + template + auto format(const detail::styled_arg& arg, FormatContext& ctx) const + -> decltype(ctx.out()) { + const auto& ts = arg.style; + const auto& value = arg.value; + auto out = ctx.out(); + + bool has_style = false; + if (ts.has_emphasis()) { + has_style = true; + auto emphasis = detail::make_emphasis(ts.get_emphasis()); + out = std::copy(emphasis.begin(), emphasis.end(), out); + } + if (ts.has_foreground()) { + has_style = true; + auto foreground = + detail::make_foreground_color(ts.get_foreground()); + out = std::copy(foreground.begin(), foreground.end(), out); + } + if (ts.has_background()) { + has_style = true; + auto background = + detail::make_background_color(ts.get_background()); + out = std::copy(background.begin(), background.end(), out); + } + out = formatter::format(value, ctx); + if (has_style) { + auto reset_color = string_view("\x1b[0m"); + out = std::copy(reset_color.begin(), reset_color.end(), out); + } + return out; + } +}; + +/** + \rst + Returns an argument that will be formatted using ANSI escape sequences, + to be used in a formatting function. + + **Example**:: + + fmt::print("Elapsed time: {0:.2f} seconds", + fmt::styled(1.23, fmt::fg(fmt::color::green) | + fmt::bg(fmt::color::blue))); + \endrst + */ +template +FMT_CONSTEXPR auto styled(const T& value, text_style ts) + -> detail::styled_arg> { + return detail::styled_arg>{value, ts}; +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_COLOR_H_ diff --git a/thirdparty/fmt/include/fmt/compile.h b/thirdparty/fmt/include/fmt/compile.h index 94e13c02d..3b3f166e0 100644 --- a/thirdparty/fmt/include/fmt/compile.h +++ b/thirdparty/fmt/include/fmt/compile.h @@ -14,89 +14,11 @@ FMT_BEGIN_NAMESPACE namespace detail { template -FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end, - counting_iterator it) { +FMT_CONSTEXPR inline auto copy_str(InputIt begin, InputIt end, + counting_iterator it) -> counting_iterator { return it + (end - begin); } -template class truncating_iterator_base { - protected: - OutputIt out_; - size_t limit_; - size_t count_ = 0; - - truncating_iterator_base() : out_(), limit_(0) {} - - truncating_iterator_base(OutputIt out, size_t limit) - : out_(out), limit_(limit) {} - - public: - using iterator_category = std::output_iterator_tag; - using value_type = typename std::iterator_traits::value_type; - using difference_type = std::ptrdiff_t; - using pointer = void; - using reference = void; - FMT_UNCHECKED_ITERATOR(truncating_iterator_base); - - OutputIt base() const { return out_; } - size_t count() const { return count_; } -}; - -// An output iterator that truncates the output and counts the number of objects -// written to it. -template ::value_type>::type> -class truncating_iterator; - -template -class truncating_iterator - : public truncating_iterator_base { - mutable typename truncating_iterator_base::value_type blackhole_; - - public: - using value_type = typename truncating_iterator_base::value_type; - - truncating_iterator() = default; - - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base(out, limit) {} - - truncating_iterator& operator++() { - if (this->count_++ < this->limit_) ++this->out_; - return *this; - } - - truncating_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - value_type& operator*() const { - return this->count_ < this->limit_ ? *this->out_ : blackhole_; - } -}; - -template -class truncating_iterator - : public truncating_iterator_base { - public: - truncating_iterator() = default; - - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base(out, limit) {} - - template truncating_iterator& operator=(T val) { - if (this->count_++ < this->limit_) *this->out_++ = val; - return *this; - } - - truncating_iterator& operator++() { return *this; } - truncating_iterator& operator++(int) { return *this; } - truncating_iterator& operator*() { return *this; } -}; - // A compile-time string which is compiled into fast formatting code. class compiled_string {}; @@ -135,7 +57,7 @@ struct udl_compiled_string : compiled_string { #endif template -const T& first(const T& value, const Tail&...) { +auto first(const T& value, const Tail&...) -> const T& { return value; } @@ -196,7 +118,8 @@ template struct code_unit { template constexpr OutputIt format(OutputIt out, const Args&...) const { - return write(out, value); + *out++ = value; + return out; } }; @@ -220,7 +143,12 @@ template struct field { template constexpr OutputIt format(OutputIt out, const Args&... args) const { - return write(out, get_arg_checked(args...)); + const T& arg = get_arg_checked(args...); + if constexpr (std::is_convertible_v>) { + auto s = basic_string_view(arg); + return copy_str(s.begin(), s.end(), out); + } + return write(out, arg); } }; @@ -448,20 +376,18 @@ constexpr auto compile_format_string(S format_str) { } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { constexpr auto arg_index = get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); - if constexpr (arg_index != invalid_arg_index) { + if constexpr (arg_index >= 0) { constexpr auto next_id = ID != manual_indexing_id ? ID + 1 : manual_indexing_id; return parse_replacement_field_then_tail< decltype(get_type::value), Args, arg_id_end_pos, arg_index, next_id>(format_str); - } else { - if constexpr (c == '}') { - return parse_tail( - runtime_named_field{arg_id_result.arg_id.val.name}, - format_str); - } else if constexpr (c == ':') { - return unknown_format(); // no type info for specs parsing - } + } else if constexpr (c == '}') { + return parse_tail( + runtime_named_field{arg_id_result.arg_id.val.name}, + format_str); + } else if constexpr (c == ':') { + return unknown_format(); // no type info for specs parsing } } } @@ -562,17 +488,19 @@ FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { template ::value)> -format_to_n_result format_to_n(OutputIt out, size_t n, - const S& format_str, Args&&... args) { - auto it = fmt::format_to(detail::truncating_iterator(out, n), - format_str, std::forward(args)...); - return {it.base(), it.count()}; +auto format_to_n(OutputIt out, size_t n, const S& format_str, Args&&... args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + fmt::format_to(std::back_inserter(buf), format_str, + std::forward(args)...); + return {buf.out(), buf.count()}; } template ::value)> -FMT_CONSTEXPR20 size_t formatted_size(const S& format_str, - const Args&... args) { +FMT_CONSTEXPR20 auto formatted_size(const S& format_str, const Args&... args) + -> size_t { return fmt::format_to(detail::counting_iterator(), format_str, args...) .count(); } diff --git a/thirdparty/fmt/include/fmt/core.h b/thirdparty/fmt/include/fmt/core.h index 46723d598..b51c1406a 100644 --- a/thirdparty/fmt/include/fmt/core.h +++ b/thirdparty/fmt/include/fmt/core.h @@ -13,11 +13,12 @@ #include // std::strlen #include #include +#include // std::addressof #include #include // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 100000 +#define FMT_VERSION 100201 #if defined(__clang__) && !defined(__ibmxl__) # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) @@ -92,7 +93,7 @@ #ifndef FMT_USE_CONSTEXPR # if (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 || \ (FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L)) && \ - !FMT_ICC_VERSION && !defined(__NVCC__) + !FMT_ICC_VERSION && (!defined(__NVCC__) || FMT_CPLUSPLUS >= 202002L) # define FMT_USE_CONSTEXPR 1 # else # define FMT_USE_CONSTEXPR 0 @@ -104,9 +105,12 @@ # define FMT_CONSTEXPR #endif -#if ((FMT_CPLUSPLUS >= 202002L) && \ - (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9)) || \ - (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002) +#if (FMT_CPLUSPLUS >= 202002L || \ + (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)) && \ + ((!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 10) && \ + (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 10000) && \ + (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1928)) && \ + defined(__cpp_lib_is_constant_evaluated) # define FMT_CONSTEXPR20 constexpr #else # define FMT_CONSTEXPR20 @@ -162,9 +166,6 @@ # endif #endif -// An inline std::forward replacement. -#define FMT_FORWARD(...) static_cast(__VA_ARGS__) - #ifdef _MSC_VER # define FMT_UNCHECKED_ITERATOR(It) \ using _Unchecked_type = It // Mark iterator as checked. @@ -181,24 +182,26 @@ } #endif -#ifndef FMT_MODULE_EXPORT -# define FMT_MODULE_EXPORT +#ifndef FMT_EXPORT +# define FMT_EXPORT # define FMT_BEGIN_EXPORT # define FMT_END_EXPORT #endif +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_VISIBILITY(value) __attribute__((visibility(value))) +#else +# define FMT_VISIBILITY(value) +#endif + #if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# ifdef FMT_LIB_EXPORT +# if defined(FMT_LIB_EXPORT) # define FMT_API __declspec(dllexport) # elif defined(FMT_SHARED) # define FMT_API __declspec(dllimport) # endif -#else -# if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) -# if defined(__GNUC__) || defined(__clang__) -# define FMT_API __attribute__((visibility("default"))) -# endif -# endif +#elif defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_API FMT_VISIBILITY("default") #endif #ifndef FMT_API # define FMT_API @@ -224,8 +227,9 @@ __apple_build_version__ >= 14000029L) && \ FMT_CPLUSPLUS >= 202002L) || \ (defined(__cpp_consteval) && \ - (!FMT_MSC_VERSION || _MSC_FULL_VER >= 193030704)) -// consteval is broken in MSVC before VS2022 and Apple clang before 14. + (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1929)) +// consteval is broken in MSVC before VS2019 version 16.10 and Apple clang +// before 14. # define FMT_CONSTEVAL consteval # define FMT_HAS_CONSTEVAL # else @@ -244,10 +248,13 @@ # endif #endif -#if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L -# define FMT_INLINE_VARIABLE inline -#else -# define FMT_INLINE_VARIABLE +// GCC < 5 requires this-> in decltype +#ifndef FMT_DECLTYPE_THIS +# if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +# define FMT_DECLTYPE_THIS this-> +# else +# define FMT_DECLTYPE_THIS +# endif #endif // Enable minimal optimizations for more compact code in debug mode. @@ -271,11 +278,18 @@ template using remove_const_t = typename std::remove_const::type; template using remove_cvref_t = typename std::remove_cv>::type; -template struct type_identity { using type = T; }; +template struct type_identity { + using type = T; +}; template using type_identity_t = typename type_identity::type; template using underlying_t = typename std::underlying_type::type; +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; +template +struct is_contiguous> : std::true_type {}; + struct monostate { constexpr monostate() {} }; @@ -289,8 +303,11 @@ struct monostate { # define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 #endif +// This is defined in core.h instead of format.h to avoid injecting in std. +// It is a template to avoid undesirable implicit conversions to std::byte. #ifdef __cpp_lib_byte -inline auto format_as(std::byte b) -> unsigned char { +template ::value)> +inline auto format_as(T b) -> unsigned char { return static_cast(b); } #endif @@ -394,7 +411,7 @@ FMT_CONSTEXPR inline auto is_utf8() -> bool { compiled with a different ``-std`` option than the client code (which is not recommended). */ -FMT_MODULE_EXPORT +FMT_EXPORT template class basic_string_view { private: const Char* data_; @@ -454,15 +471,15 @@ template class basic_string_view { size_ -= n; } - FMT_CONSTEXPR_CHAR_TRAITS bool starts_with( - basic_string_view sv) const noexcept { + FMT_CONSTEXPR_CHAR_TRAITS auto starts_with( + basic_string_view sv) const noexcept -> bool { return size_ >= sv.size_ && std::char_traits::compare(data_, sv.data_, sv.size_) == 0; } - FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(Char c) const noexcept { + FMT_CONSTEXPR_CHAR_TRAITS auto starts_with(Char c) const noexcept -> bool { return size_ >= 1 && std::char_traits::eq(*data_, c); } - FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(const Char* s) const { + FMT_CONSTEXPR_CHAR_TRAITS auto starts_with(const Char* s) const -> bool { return starts_with(basic_string_view(s)); } @@ -497,11 +514,11 @@ template class basic_string_view { } }; -FMT_MODULE_EXPORT +FMT_EXPORT using string_view = basic_string_view; /** Specifies if ``T`` is a character type. Can be specialized by users. */ -FMT_MODULE_EXPORT +FMT_EXPORT template struct is_char : std::false_type {}; template <> struct is_char : std::true_type {}; @@ -600,10 +617,10 @@ FMT_TYPE_CONSTANT(const Char*, cstring_type); FMT_TYPE_CONSTANT(basic_string_view, string_type); FMT_TYPE_CONSTANT(const void*, pointer_type); -constexpr bool is_integral_type(type t) { +constexpr auto is_integral_type(type t) -> bool { return t > type::none_type && t <= type::last_integer_type; } -constexpr bool is_arithmetic_type(type t) { +constexpr auto is_arithmetic_type(type t) -> bool { return t > type::none_type && t <= type::last_numeric_type; } @@ -627,6 +644,7 @@ enum { pointer_set = set(type::pointer_type) }; +// DEPRECATED! FMT_NORETURN FMT_API void throw_format_error(const char* message); struct error_handler { @@ -639,6 +657,9 @@ struct error_handler { }; } // namespace detail +/** Throws ``format_error`` with a given message. */ +using detail::throw_format_error; + /** String's character type. */ template using char_t = typename detail::char_t_impl::type; @@ -649,7 +670,7 @@ template using char_t = typename detail::char_t_impl::type; You can use the ``format_parse_context`` type alias for ``char`` instead. \endrst */ -FMT_MODULE_EXPORT +FMT_EXPORT template class basic_format_parse_context { private: basic_string_view format_str_; @@ -715,7 +736,7 @@ template class basic_format_parse_context { FMT_CONSTEXPR void check_dynamic_spec(int arg_id); }; -FMT_MODULE_EXPORT +FMT_EXPORT using format_parse_context = basic_format_parse_context; namespace detail { @@ -756,72 +777,6 @@ class compile_parse_context : public basic_format_parse_context { #endif } }; -} // namespace detail - -template -FMT_CONSTEXPR void basic_format_parse_context::do_check_arg_id(int id) { - // Argument id is only checked at compile-time during parsing because - // formatting has its own validation. - if (detail::is_constant_evaluated() && - (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { - using context = detail::compile_parse_context; - if (id >= static_cast(this)->num_args()) - detail::throw_format_error("argument not found"); - } -} - -template -FMT_CONSTEXPR void basic_format_parse_context::check_dynamic_spec( - int arg_id) { - if (detail::is_constant_evaluated() && - (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { - using context = detail::compile_parse_context; - static_cast(this)->check_dynamic_spec(arg_id); - } -} - -FMT_MODULE_EXPORT template class basic_format_arg; -FMT_MODULE_EXPORT template class basic_format_args; -FMT_MODULE_EXPORT template class dynamic_format_arg_store; - -// A formatter for objects of type T. -FMT_MODULE_EXPORT -template -struct formatter { - // A deleted default constructor indicates a disabled formatter. - formatter() = delete; -}; - -// Specifies if T has an enabled formatter specialization. A type can be -// formattable even if it doesn't have a formatter e.g. via a conversion. -template -using has_formatter = - std::is_constructible>; - -// Checks whether T is a container with contiguous storage. -template struct is_contiguous : std::false_type {}; -template -struct is_contiguous> : std::true_type {}; - -class appender; - -namespace detail { - -template -constexpr auto has_const_formatter_impl(T*) - -> decltype(typename Context::template formatter_type().format( - std::declval(), std::declval()), - true) { - return true; -} -template -constexpr auto has_const_formatter_impl(...) -> bool { - return false; -} -template -constexpr auto has_const_formatter() -> bool { - return has_const_formatter_impl(static_cast(nullptr)); -} // Extracts a reference to the container from back_insert_iterator. template @@ -867,7 +822,7 @@ template class buffer { protected: // Don't initialize ptr_ since it is not accessed to save a few cycles. FMT_MSC_WARNING(suppress : 26495) - buffer(size_t sz) noexcept : size_(sz), capacity_(sz) {} + FMT_CONSTEXPR buffer(size_t sz) noexcept : size_(sz), capacity_(sz) {} FMT_CONSTEXPR20 buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) noexcept : ptr_(p), size_(sz), capacity_(cap) {} @@ -882,6 +837,7 @@ template class buffer { } /** Increases the buffer capacity to hold at least *capacity* elements. */ + // DEPRECATED! virtual FMT_CONSTEXPR20 void grow(size_t capacity) = 0; public: @@ -903,10 +859,8 @@ template class buffer { /** Returns the capacity of this buffer. */ constexpr auto capacity() const noexcept -> size_t { return capacity_; } - /** Returns a pointer to the buffer data. */ + /** Returns a pointer to the buffer data (not null-terminated). */ FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } - - /** Returns a pointer to the buffer data. */ FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } /** Clears this buffer. */ @@ -1099,6 +1053,79 @@ template class counting_buffer final : public buffer { auto count() -> size_t { return count_ + this->size(); } }; +} // namespace detail + +template +FMT_CONSTEXPR void basic_format_parse_context::do_check_arg_id(int id) { + // Argument id is only checked at compile-time during parsing because + // formatting has its own validation. + if (detail::is_constant_evaluated() && + (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { + using context = detail::compile_parse_context; + if (id >= static_cast(this)->num_args()) + detail::throw_format_error("argument not found"); + } +} + +template +FMT_CONSTEXPR void basic_format_parse_context::check_dynamic_spec( + int arg_id) { + if (detail::is_constant_evaluated() && + (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { + using context = detail::compile_parse_context; + static_cast(this)->check_dynamic_spec(arg_id); + } +} + +FMT_EXPORT template class basic_format_arg; +FMT_EXPORT template class basic_format_args; +FMT_EXPORT template class dynamic_format_arg_store; + +// A formatter for objects of type T. +FMT_EXPORT +template +struct formatter { + // A deleted default constructor indicates a disabled formatter. + formatter() = delete; +}; + +// Specifies if T has an enabled formatter specialization. A type can be +// formattable even if it doesn't have a formatter e.g. via a conversion. +template +using has_formatter = + std::is_constructible>; + +// An output iterator that appends to a buffer. +// It is used to reduce symbol sizes for the common case. +class appender : public std::back_insert_iterator> { + using base = std::back_insert_iterator>; + + public: + using std::back_insert_iterator>::back_insert_iterator; + appender(base it) noexcept : base(it) {} + FMT_UNCHECKED_ITERATOR(appender); + + auto operator++() noexcept -> appender& { return *this; } + auto operator++(int) noexcept -> appender { return *this; } +}; + +namespace detail { + +template +constexpr auto has_const_formatter_impl(T*) + -> decltype(typename Context::template formatter_type().format( + std::declval(), std::declval()), + true) { + return true; +} +template +constexpr auto has_const_formatter_impl(...) -> bool { + return false; +} +template +constexpr auto has_const_formatter() -> bool { + return has_const_formatter_impl(static_cast(nullptr)); +} template using buffer_appender = conditional_t::value, appender, @@ -1274,9 +1301,9 @@ template class value { FMT_INLINE value(const named_arg_info* args, size_t size) : named_args{args, size} {} - template FMT_CONSTEXPR FMT_INLINE value(T& val) { - using value_type = remove_cvref_t; - custom.value = const_cast(&val); + template FMT_CONSTEXPR20 FMT_INLINE value(T& val) { + using value_type = remove_const_t; + custom.value = const_cast(std::addressof(val)); // Get the formatter type through the context to allow different contexts // have different extension points, e.g. `formatter` for `format` and // `printf_formatter` for `printf`. @@ -1297,13 +1324,11 @@ template class value { parse_ctx.advance_to(f.parse(parse_ctx)); using qualified_type = conditional_t(), const T, T>; + // Calling format through a mutable reference is deprecated. ctx.advance_to(f.format(*static_cast(arg), ctx)); } }; -template -FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg; - // To minimize the number of types we need to deal with, long is translated // either to int or to long long depending on its size. enum { long_short = sizeof(long) == sizeof(int) }; @@ -1313,7 +1338,7 @@ using ulong_type = conditional_t; template struct format_as_result { template ::value || std::is_class::value)> - static auto map(U*) -> decltype(format_as(std::declval())); + static auto map(U*) -> remove_cvref_t()))>; static auto map(...) -> void; using type = decltype(map(static_cast(nullptr))); @@ -1415,9 +1440,8 @@ template struct arg_mapper { FMT_ENABLE_IF( std::is_pointer::value || std::is_member_pointer::value || std::is_function::type>::value || - (std::is_convertible::value && - !std::is_convertible::value && - !has_formatter::value))> + (std::is_array::value && + !std::is_convertible::value))> FMT_CONSTEXPR auto map(const T&) -> unformattable_pointer { return {}; } @@ -1431,39 +1455,39 @@ template struct arg_mapper { // Only map owning types because mapping views can be unsafe. template , FMT_ENABLE_IF(std::is_arithmetic::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> decltype(this->map(U())) { + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> decltype(FMT_DECLTYPE_THIS map(U())) { return map(format_as(val)); } - template > - struct formattable - : bool_constant() || - (has_formatter::value && - !std::is_const>::value)> {}; + template > + struct formattable : bool_constant() || + (has_formatter::value && + !std::is_const::value)> {}; template ::value)> - FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { + FMT_CONSTEXPR FMT_INLINE auto do_map(T& val) -> T& { return val; } template ::value)> - FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable { + FMT_CONSTEXPR FMT_INLINE auto do_map(T&) -> unformattable { return {}; } - template , + template , FMT_ENABLE_IF((std::is_class::value || std::is_enum::value || std::is_union::value) && !is_string::value && !is_char::value && !is_named_arg::value && !std::is_arithmetic>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(T&& val) - -> decltype(this->do_map(std::forward(val))) { - return do_map(std::forward(val)); + FMT_CONSTEXPR FMT_INLINE auto map(T& val) + -> decltype(FMT_DECLTYPE_THIS do_map(val)) { + return do_map(val); } template ::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) - -> decltype(this->map(named_arg.value)) { + -> decltype(FMT_DECLTYPE_THIS map(named_arg.value)) { return map(named_arg.value); } @@ -1481,31 +1505,132 @@ enum { packed_arg_bits = 4 }; enum { max_packed_args = 62 / packed_arg_bits }; enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; -} // namespace detail -// An output iterator that appends to a buffer. -// It is used to reduce symbol sizes for the common case. -class appender : public std::back_insert_iterator> { - using base = std::back_insert_iterator>; +template +auto copy_str(InputIt begin, InputIt end, appender out) -> appender { + get_container(out).append(begin, end); + return out; +} +template +auto copy_str(InputIt begin, InputIt end, + std::back_insert_iterator out) + -> std::back_insert_iterator { + get_container(out).append(begin, end); + return out; +} + +template +FMT_CONSTEXPR auto copy_str(R&& rng, OutputIt out) -> OutputIt { + return detail::copy_str(rng.begin(), rng.end(), out); +} + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +// A workaround for gcc 4.8 to make void_t work in a SFINAE context. +template struct void_t_impl { + using type = void; +}; +template using void_t = typename void_t_impl::type; +#else +template using void_t = void; +#endif + +template +struct is_output_iterator : std::false_type {}; + +template +struct is_output_iterator< + It, T, + void_t::iterator_category, + decltype(*std::declval() = std::declval())>> + : std::true_type {}; + +template struct is_back_insert_iterator : std::false_type {}; +template +struct is_back_insert_iterator> + : std::true_type {}; + +// A type-erased reference to an std::locale to avoid a heavy include. +class locale_ref { + private: + const void* locale_; // A type-erased pointer to std::locale. public: - using std::back_insert_iterator>::back_insert_iterator; - appender(base it) noexcept : base(it) {} - FMT_UNCHECKED_ITERATOR(appender); + constexpr FMT_INLINE locale_ref() : locale_(nullptr) {} + template explicit locale_ref(const Locale& loc); - auto operator++() noexcept -> appender& { return *this; } - auto operator++(int) noexcept -> appender { return *this; } + explicit operator bool() const noexcept { return locale_ != nullptr; } + + template auto get() const -> Locale; }; -// A formatting argument. It is a trivially copyable/constructible type to -// allow storage in basic_memory_buffer. +template constexpr auto encode_types() -> unsigned long long { + return 0; +} + +template +constexpr auto encode_types() -> unsigned long long { + return static_cast(mapped_type_constant::value) | + (encode_types() << packed_arg_bits); +} + +#if defined(__cpp_if_constexpr) +// This type is intentionally undefined, only used for errors +template struct type_is_unformattable_for; +#endif + +template +FMT_CONSTEXPR FMT_INLINE auto make_arg(T& val) -> value { + using arg_type = remove_cvref_t().map(val))>; + + constexpr bool formattable_char = + !std::is_same::value; + static_assert(formattable_char, "Mixing character types is disallowed."); + + // Formatting of arbitrary pointers is disallowed. If you want to format a + // pointer cast it to `void*` or `const void*`. In particular, this forbids + // formatting of `[const] volatile char*` printed as bool by iostreams. + constexpr bool formattable_pointer = + !std::is_same::value; + static_assert(formattable_pointer, + "Formatting of non-void pointers is disallowed."); + + constexpr bool formattable = !std::is_same::value; +#if defined(__cpp_if_constexpr) + if constexpr (!formattable) { + type_is_unformattable_for _; + } +#endif + static_assert( + formattable, + "Cannot format an argument. To make type T formattable provide a " + "formatter specialization: https://fmt.dev/latest/api.html#udt"); + return {arg_mapper().map(val)}; +} + +template +FMT_CONSTEXPR auto make_arg(T& val) -> basic_format_arg { + auto arg = basic_format_arg(); + arg.type_ = mapped_type_constant::value; + arg.value_ = make_arg(val); + return arg; +} + +template +FMT_CONSTEXPR inline auto make_arg(T& val) -> basic_format_arg { + return make_arg(val); +} +} // namespace detail +FMT_BEGIN_EXPORT + +// A formatting argument. Context is a template parameter for the compiled API +// where output can be unbuffered. template class basic_format_arg { private: detail::value value_; detail::type type_; template - friend FMT_CONSTEXPR auto detail::make_arg(T&& value) + friend FMT_CONSTEXPR auto detail::make_arg(T& value) -> basic_format_arg; template @@ -1550,6 +1675,15 @@ template class basic_format_arg { auto is_arithmetic() const -> bool { return detail::is_arithmetic_type(type_); } + + FMT_INLINE auto format_custom(const char_type* parse_begin, + typename Context::parse_context_type& parse_ctx, + Context& ctx) -> bool { + if (type_ != detail::type::custom_type) return false; + parse_ctx.advance_to(parse_begin); + value_.custom.format(value_.custom.value, parse_ctx, ctx); + return true; + } }; /** @@ -1559,7 +1693,7 @@ template class basic_format_arg { ``vis(value)`` will be called with the value of type ``double``. \endrst */ -FMT_MODULE_EXPORT +// DEPRECATED! template FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { @@ -1601,124 +1735,6 @@ FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( return vis(monostate()); } -namespace detail { - -template -auto copy_str(InputIt begin, InputIt end, appender out) -> appender { - get_container(out).append(begin, end); - return out; -} - -template -FMT_CONSTEXPR auto copy_str(R&& rng, OutputIt out) -> OutputIt { - return detail::copy_str(rng.begin(), rng.end(), out); -} - -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 -// A workaround for gcc 4.8 to make void_t work in a SFINAE context. -template struct void_t_impl { using type = void; }; -template using void_t = typename void_t_impl::type; -#else -template using void_t = void; -#endif - -template -struct is_output_iterator : std::false_type {}; - -template -struct is_output_iterator< - It, T, - void_t::iterator_category, - decltype(*std::declval() = std::declval())>> - : std::true_type {}; - -template struct is_back_insert_iterator : std::false_type {}; -template -struct is_back_insert_iterator> - : std::true_type {}; - -template -struct is_contiguous_back_insert_iterator : std::false_type {}; -template -struct is_contiguous_back_insert_iterator> - : is_contiguous {}; -template <> -struct is_contiguous_back_insert_iterator : std::true_type {}; - -// A type-erased reference to an std::locale to avoid a heavy include. -class locale_ref { - private: - const void* locale_; // A type-erased pointer to std::locale. - - public: - constexpr FMT_INLINE locale_ref() : locale_(nullptr) {} - template explicit locale_ref(const Locale& loc); - - explicit operator bool() const noexcept { return locale_ != nullptr; } - - template auto get() const -> Locale; -}; - -template constexpr auto encode_types() -> unsigned long long { - return 0; -} - -template -constexpr auto encode_types() -> unsigned long long { - return static_cast(mapped_type_constant::value) | - (encode_types() << packed_arg_bits); -} - -template -FMT_CONSTEXPR FMT_INLINE auto make_value(T&& val) -> value { - auto&& arg = arg_mapper().map(FMT_FORWARD(val)); - using arg_type = remove_cvref_t; - - constexpr bool formattable_char = - !std::is_same::value; - static_assert(formattable_char, "Mixing character types is disallowed."); - - // Formatting of arbitrary pointers is disallowed. If you want to format a - // pointer cast it to `void*` or `const void*`. In particular, this forbids - // formatting of `[const] volatile char*` printed as bool by iostreams. - constexpr bool formattable_pointer = - !std::is_same::value; - static_assert(formattable_pointer, - "Formatting of non-void pointers is disallowed."); - - constexpr bool formattable = !std::is_same::value; - static_assert( - formattable, - "Cannot format an argument. To make type T formattable provide a " - "formatter specialization: https://fmt.dev/latest/api.html#udt"); - return {arg}; -} - -template -FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg { - auto arg = basic_format_arg(); - arg.type_ = mapped_type_constant::value; - arg.value_ = make_value(value); - return arg; -} - -// The DEPRECATED type template parameter is there to avoid an ODR violation -// when using a fallback formatter in one translation unit and an implicit -// conversion in another (not recommended). -template -FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value { - return make_value(val); -} - -template -FMT_CONSTEXPR inline auto make_arg(T&& value) -> basic_format_arg { - return make_arg(value); -} -} // namespace detail -FMT_BEGIN_EXPORT - // Formatting context. template class basic_format_context { private: @@ -1756,6 +1772,7 @@ template class basic_format_context { } auto args() const -> const format_args& { return args_; } + // DEPRECATED! FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; } void on_error(const char* message) { error_handler().on_error(message); } @@ -1778,7 +1795,7 @@ using format_context = buffer_context; template using is_formattable = bool_constant>() - .map(std::declval()))>::value>; + .map(std::declval()))>::value>; /** \rst @@ -1796,7 +1813,7 @@ class format_arg_store { private: static const size_t num_args = sizeof...(Args); - static const size_t num_named_args = detail::count_named_args(); + static constexpr size_t num_named_args = detail::count_named_args(); static const bool is_packed = num_args <= detail::max_packed_args; using value_type = conditional_t, @@ -1817,16 +1834,14 @@ class format_arg_store public: template - FMT_CONSTEXPR FMT_INLINE format_arg_store(T&&... args) + FMT_CONSTEXPR FMT_INLINE format_arg_store(T&... args) : #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 basic_format_args(*this), #endif - data_{detail::make_arg< - is_packed, Context, - detail::mapped_type_constant, Context>::value>( - FMT_FORWARD(args))...} { - detail::init_named_args(data_.named_args(), 0, 0, args...); + data_{detail::make_arg(args)...} { + if (detail::const_check(num_named_args != 0)) + detail::init_named_args(data_.named_args(), 0, 0, args...); } }; @@ -1834,14 +1849,15 @@ class format_arg_store \rst Constructs a `~fmt::format_arg_store` object that contains references to arguments and can be implicitly converted to `~fmt::format_args`. `Context` - can be omitted in which case it defaults to `~fmt::context`. + can be omitted in which case it defaults to `~fmt::format_context`. See `~fmt::arg` for lifetime considerations. \endrst */ +// Arguments are taken by lvalue references to avoid some lifetime issues. template -constexpr auto make_format_args(T&&... args) +constexpr auto make_format_args(T&... args) -> format_arg_store...> { - return {FMT_FORWARD(args)...}; + return {args...}; } /** @@ -1869,7 +1885,7 @@ FMT_END_EXPORT ``vformat``:: void vlog(string_view format_str, format_args args); // OK - format_args args = make_format_args(42); // Error: dangling reference + format_args args = make_format_args(); // Error: dangling reference \endrst */ template class basic_format_args { @@ -1986,7 +2002,7 @@ template class basic_format_args { /** An alias to ``basic_format_args``. */ // A separate type would result in shorter symbols but break ABI compatibility // between clang and gcc on ARM (#1919). -FMT_MODULE_EXPORT using format_args = basic_format_args; +FMT_EXPORT using format_args = basic_format_args; // We cannot use enum classes as bit fields because of a gcc bug, so we put them // in namespaces instead (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414). @@ -2318,9 +2334,12 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( dynamic_format_specs& specs; type arg_type; - FMT_CONSTEXPR auto operator()(pres type, int set) -> const Char* { - if (!in(arg_type, set)) throw_format_error("invalid format specifier"); - specs.type = type; + FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* { + if (!in(arg_type, set)) { + if (arg_type == type::none_type) return begin; + throw_format_error("invalid format specifier"); + } + specs.type = pres_type; return begin + 1; } } parse_presentation_type{begin, specs, arg_type}; @@ -2337,6 +2356,7 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( case '+': case '-': case ' ': + if (arg_type == type::none_type) return begin; enter_state(state::sign, in(arg_type, sint_set | float_set)); switch (c) { case '+': @@ -2352,14 +2372,17 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( ++begin; break; case '#': + if (arg_type == type::none_type) return begin; enter_state(state::hash, is_arithmetic_type(arg_type)); specs.alt = true; ++begin; break; case '0': enter_state(state::zero); - if (!is_arithmetic_type(arg_type)) + if (!is_arithmetic_type(arg_type)) { + if (arg_type == type::none_type) return begin; throw_format_error("format specifier requires numeric argument"); + } if (specs.align == align::none) { // Ignore 0 if align is specified for compatibility with std::format. specs.align = align::numeric; @@ -2381,12 +2404,14 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( begin = parse_dynamic_spec(begin, end, specs.width, specs.width_ref, ctx); break; case '.': + if (arg_type == type::none_type) return begin; enter_state(state::precision, in(arg_type, float_set | string_set | cstring_set)); begin = parse_precision(begin, end, specs.precision, specs.precision_ref, ctx); break; case 'L': + if (arg_type == type::none_type) return begin; enter_state(state::locale, is_arithmetic_type(arg_type)); specs.localized = true; ++begin; @@ -2420,6 +2445,8 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( case 'G': return parse_presentation_type(pres::general_upper, float_set); case 'c': + if (arg_type == type::bool_type) + throw_format_error("invalid format specifier"); return parse_presentation_type(pres::chr, integral_set); case 's': return parse_presentation_type(pres::string, @@ -2558,7 +2585,17 @@ FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) mapped_type_constant::value != type::custom_type, decltype(arg_mapper().map(std::declval())), typename strip_named_arg::type>; +#if defined(__cpp_if_constexpr) + if constexpr (std::is_default_constructible< + formatter>::value) { + return formatter().parse(ctx); + } else { + type_is_unformattable_for _; + return ctx.begin(); + } +#else return formatter().parse(ctx); +#endif } // Checks char specs and returns true iff the presentation type is char-like. @@ -2574,8 +2611,6 @@ FMT_CONSTEXPR auto check_char_specs(const format_specs& specs) -> bool { return true; } -constexpr FMT_INLINE_VARIABLE int invalid_arg_index = -1; - #if FMT_USE_NONTYPE_TEMPLATE_ARGS template constexpr auto get_arg_index_by_name(basic_string_view name) -> int { @@ -2585,7 +2620,7 @@ constexpr auto get_arg_index_by_name(basic_string_view name) -> int { if constexpr (sizeof...(Args) > 0) return get_arg_index_by_name(name); (void)name; // Workaround an MSVC bug about "unused" parameter. - return invalid_arg_index; + return -1; } #endif @@ -2596,7 +2631,7 @@ FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { return get_arg_index_by_name<0, Args...>(name); #endif (void)name; - return invalid_arg_index; + return -1; } template class format_string_checker { @@ -2610,15 +2645,15 @@ template class format_string_checker { // needed for compile-time checks: https://godbolt.org/z/GvWzcTjh1. using parse_func = const Char* (*)(parse_context_type&); + type types_[num_args > 0 ? static_cast(num_args) : 1]; parse_context_type context_; parse_func parse_funcs_[num_args > 0 ? static_cast(num_args) : 1]; - type types_[num_args > 0 ? static_cast(num_args) : 1]; public: explicit FMT_CONSTEXPR format_string_checker(basic_string_view fmt) - : context_(fmt, num_args, types_), - parse_funcs_{&parse_format_specs...}, - types_{mapped_type_constant>::value...} {} + : types_{mapped_type_constant>::value...}, + context_(fmt, num_args, types_), + parse_funcs_{&parse_format_specs...} {} FMT_CONSTEXPR void on_text(const Char*, const Char*) {} @@ -2629,7 +2664,7 @@ template class format_string_checker { FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { #if FMT_USE_NONTYPE_TEMPLATE_ARGS auto index = get_arg_index_by_name(id); - if (index == invalid_arg_index) on_error("named argument is not found"); + if (index < 0) on_error("named argument is not found"); return index; #else (void)id; @@ -2638,7 +2673,9 @@ template class format_string_checker { #endif } - FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} + FMT_CONSTEXPR void on_replacement_field(int id, const Char* begin) { + on_format_specs(id, begin, begin); // Call parse() on empty specs. + } FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) -> const Char* { @@ -2675,7 +2712,9 @@ template struct vformat_args { using type = basic_format_args< basic_format_context>, Char>>; }; -template <> struct vformat_args { using type = format_args; }; +template <> struct vformat_args { + using type = format_args; +}; // Use vformat_args and avoid type_identity to keep symbols short. template @@ -2721,27 +2760,6 @@ struct formatter decltype(ctx.out()); }; -#define FMT_FORMAT_AS(Type, Base) \ - template \ - struct formatter : formatter { \ - template \ - auto format(const Type& val, FormatContext& ctx) const \ - -> decltype(ctx.out()) { \ - return formatter::format(static_cast(val), ctx); \ - } \ - } - -FMT_FORMAT_AS(signed char, int); -FMT_FORMAT_AS(unsigned char, unsigned); -FMT_FORMAT_AS(short, int); -FMT_FORMAT_AS(unsigned short, unsigned); -FMT_FORMAT_AS(long, long long); -FMT_FORMAT_AS(unsigned long, unsigned long long); -FMT_FORMAT_AS(Char*, const Char*); -FMT_FORMAT_AS(std::basic_string, basic_string_view); -FMT_FORMAT_AS(std::nullptr_t, const void*); -FMT_FORMAT_AS(detail::std_string_view, basic_string_view); - template struct runtime_format_string { basic_string_view str; }; diff --git a/thirdparty/fmt/include/fmt/format-inl.h b/thirdparty/fmt/include/fmt/format-inl.h index 5bae3c7b2..efac5d1f8 100644 --- a/thirdparty/fmt/include/fmt/format-inl.h +++ b/thirdparty/fmt/include/fmt/format-inl.h @@ -18,7 +18,7 @@ # include #endif -#ifdef _WIN32 +#if defined(_WIN32) && !defined(FMT_WINDOWS_NO_WCHAR) # include // _isatty #endif @@ -58,8 +58,8 @@ FMT_FUNC void format_error_code(detail::buffer& out, int error_code, error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); auto it = buffer_appender(out); if (message.size() <= inline_buffer_size - error_code_size) - format_to(it, FMT_STRING("{}{}"), message, SEP); - format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); + fmt::format_to(it, FMT_STRING("{}{}"), message, SEP); + fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); FMT_ASSERT(out.size() <= inline_buffer_size, ""); } @@ -73,9 +73,8 @@ FMT_FUNC void report_error(format_func func, int error_code, } // A wrapper around fwrite that throws on error. -inline void fwrite_fully(const void* ptr, size_t size, size_t count, - FILE* stream) { - size_t written = std::fwrite(ptr, size, count, stream); +inline void fwrite_fully(const void* ptr, size_t count, FILE* stream) { + size_t written = std::fwrite(ptr, 1, count, stream); if (written < count) FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); } @@ -86,7 +85,7 @@ locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { static_assert(std::is_same::value, ""); } -template Locale locale_ref::get() const { +template auto locale_ref::get() const -> Locale { static_assert(std::is_same::value, ""); return locale_ ? *static_cast(locale_) : std::locale(); } @@ -98,7 +97,8 @@ FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result { auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); return {std::move(grouping), thousands_sep}; } -template FMT_FUNC Char decimal_point_impl(locale_ref loc) { +template +FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char { return std::use_facet>(loc.get()) .decimal_point(); } @@ -144,24 +144,25 @@ FMT_API FMT_FUNC auto format_facet::do_put( } #endif -FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt, - format_args args) { +FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args) + -> std::system_error { auto ec = std::error_code(error_code, std::generic_category()); return std::system_error(ec, vformat(fmt, args)); } namespace detail { -template inline bool operator==(basic_fp x, basic_fp y) { +template +inline auto operator==(basic_fp x, basic_fp y) -> bool { return x.f == y.f && x.e == y.e; } // Compilers should be able to optimize this into the ror instruction. -FMT_CONSTEXPR inline uint32_t rotr(uint32_t n, uint32_t r) noexcept { +FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t { r &= 31; return (n >> r) | (n << (32 - r)); } -FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept { +FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t { r &= 63; return (n >> r) | (n << (64 - r)); } @@ -170,14 +171,14 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept { namespace dragonbox { // Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. -inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept { +inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t { return umul128_upper64(static_cast(x) << 32, y); } // Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. -inline uint128_fallback umul192_lower128(uint64_t x, - uint128_fallback y) noexcept { +inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { uint64_t high = x * y.high(); uint128_fallback high_low = umul128(x, y.low()); return {high + high_low.high(), high_low.low()}; @@ -185,12 +186,12 @@ inline uint128_fallback umul192_lower128(uint64_t x, // Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. -inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept { +inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t { return x * y; } // Various fast log computations. -inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept { +inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int { FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); return (e * 631305 - 261663) >> 21; } @@ -204,7 +205,7 @@ FMT_INLINE_VARIABLE constexpr struct { // divisible by pow(10, N). // Precondition: n <= pow(10, N + 1). template -bool check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept { +auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool { // The numbers below are chosen such that: // 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100, // 2. nm mod 2^k < m if and only if n is divisible by d, @@ -229,7 +230,7 @@ bool check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept { // Computes floor(n / pow(10, N)) for small n and N. // Precondition: n <= pow(10, N + 1). -template uint32_t small_division_by_pow10(uint32_t n) noexcept { +template auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t { constexpr auto info = div_small_pow10_infos[N - 1]; FMT_ASSERT(n <= info.divisor * 10, "n is too large"); constexpr uint32_t magic_number = @@ -238,12 +239,12 @@ template uint32_t small_division_by_pow10(uint32_t n) noexcept { } // Computes floor(n / 10^(kappa + 1)) (float) -inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) noexcept { +inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t { // 1374389535 = ceil(2^37/100) return static_cast((static_cast(n) * 1374389535) >> 37); } // Computes floor(n / 10^(kappa + 1)) (double) -inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept { +inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t { // 2361183241434822607 = ceil(2^(64+7)/1000) return umul128_upper64(n, 2361183241434822607ull) >> 7; } @@ -255,7 +256,7 @@ template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; using cache_entry_type = uint64_t; - static uint64_t get_cached_power(int k) noexcept { + static auto get_cached_power(int k) noexcept -> uint64_t { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); static constexpr const uint64_t pow10_significands[] = { @@ -297,20 +298,23 @@ template <> struct cache_accessor { bool is_integer; }; - static compute_mul_result compute_mul( - carrier_uint u, const cache_entry_type& cache) noexcept { + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { auto r = umul96_upper64(u, cache); return {static_cast(r >> 32), static_cast(r) == 0}; } - static uint32_t compute_delta(const cache_entry_type& cache, - int beta) noexcept { + static auto compute_delta(const cache_entry_type& cache, int beta) noexcept + -> uint32_t { return static_cast(cache >> (64 - 1 - beta)); } - static compute_mul_parity_result compute_mul_parity( - carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept { + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { FMT_ASSERT(beta >= 1, ""); FMT_ASSERT(beta < 64, ""); @@ -319,22 +323,22 @@ template <> struct cache_accessor { static_cast(r >> (32 - beta)) == 0}; } - static carrier_uint compute_left_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return static_cast( (cache - (cache >> (num_significand_bits() + 2))) >> (64 - num_significand_bits() - 1 - beta)); } - static carrier_uint compute_right_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return static_cast( (cache + (cache >> (num_significand_bits() + 1))) >> (64 - num_significand_bits() - 1 - beta)); } - static carrier_uint compute_round_up_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (static_cast( cache >> (64 - num_significand_bits() - 2 - beta)) + 1) / @@ -346,7 +350,7 @@ template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; using cache_entry_type = uint128_fallback; - static uint128_fallback get_cached_power(int k) noexcept { + static auto get_cached_power(int k) noexcept -> uint128_fallback { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); @@ -985,8 +989,7 @@ template <> struct cache_accessor { {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, - { 0xdb68c2ca82ed2a05, - 0xa67398db9f6820e2 } + {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}, #else {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, @@ -1071,19 +1074,22 @@ template <> struct cache_accessor { bool is_integer; }; - static compute_mul_result compute_mul( - carrier_uint u, const cache_entry_type& cache) noexcept { + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { auto r = umul192_upper128(u, cache); return {r.high(), r.low() == 0}; } - static uint32_t compute_delta(cache_entry_type const& cache, - int beta) noexcept { + static auto compute_delta(cache_entry_type const& cache, int beta) noexcept + -> uint32_t { return static_cast(cache.high() >> (64 - 1 - beta)); } - static compute_mul_parity_result compute_mul_parity( - carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept { + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { FMT_ASSERT(beta >= 1, ""); FMT_ASSERT(beta < 64, ""); @@ -1092,35 +1098,35 @@ template <> struct cache_accessor { ((r.high() << beta) | (r.low() >> (64 - beta))) == 0}; } - static carrier_uint compute_left_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (cache.high() - (cache.high() >> (num_significand_bits() + 2))) >> (64 - num_significand_bits() - 1 - beta); } - static carrier_uint compute_right_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (cache.high() + (cache.high() >> (num_significand_bits() + 1))) >> (64 - num_significand_bits() - 1 - beta); } - static carrier_uint compute_round_up_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return ((cache.high() >> (64 - num_significand_bits() - 2 - beta)) + 1) / 2; } }; -FMT_FUNC uint128_fallback get_cached_power(int k) noexcept { +FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback { return cache_accessor::get_cached_power(k); } // Various integer checks template -bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept { +auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool { const int case_shorter_interval_left_endpoint_lower_threshold = 2; const int case_shorter_interval_left_endpoint_upper_threshold = 3; return exponent >= case_shorter_interval_left_endpoint_lower_threshold && @@ -1128,16 +1134,12 @@ bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept { } // Remove trailing zeros from n and return the number of zeros removed (float) -FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept { +FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept { FMT_ASSERT(n != 0, ""); // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. - // See https://github.com/fmtlib/fmt/issues/3163 for more details. - const uint32_t mod_inv_5 = 0xcccccccd; - // Casts are needed to workaround a bug in MSVC 19.22 and older. - const uint32_t mod_inv_25 = - static_cast(uint64_t(mod_inv_5) * mod_inv_5); + constexpr uint32_t mod_inv_5 = 0xcccccccd; + constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5 - int s = 0; while (true) { auto q = rotr(n * mod_inv_25, 2); if (q > max_value() / 100) break; @@ -1162,32 +1164,17 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { // Is n is divisible by 10^8? if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) { - // If yes, work with the quotient. + // If yes, work with the quotient... auto n32 = static_cast(nm.high() >> (90 - 64)); - - const uint32_t mod_inv_5 = 0xcccccccd; - const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5; - - int s = 8; - while (true) { - auto q = rotr(n32 * mod_inv_25, 2); - if (q > max_value() / 100) break; - n32 = q; - s += 2; - } - auto q = rotr(n32 * mod_inv_5, 1); - if (q <= max_value() / 10) { - n32 = q; - s |= 1; - } - + // ... and use the 32 bit variant of the function + int s = remove_trailing_zeros(n32, 8); n = n32; return s; } // If n is not divisible by 10^8, work with n itself. - const uint64_t mod_inv_5 = 0xcccccccccccccccd; - const uint64_t mod_inv_25 = mod_inv_5 * mod_inv_5; + constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd; + constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // mod_inv_5 * mod_inv_5 int s = 0; while (true) { @@ -1253,7 +1240,7 @@ FMT_INLINE decimal_fp shorter_interval_case(int exponent) noexcept { return ret_value; } -template decimal_fp to_decimal(T x) noexcept { +template auto to_decimal(T x) noexcept -> decimal_fp { // Step 1: integer promotion & Schubfach multiplier calculation. using carrier_uint = typename float_info::carrier_uint; @@ -1392,15 +1379,15 @@ template <> struct formatter { for (auto i = n.bigits_.size(); i > 0; --i) { auto value = n.bigits_[i - 1u]; if (first) { - out = format_to(out, FMT_STRING("{:x}"), value); + out = fmt::format_to(out, FMT_STRING("{:x}"), value); first = false; continue; } - out = format_to(out, FMT_STRING("{:08x}"), value); + out = fmt::format_to(out, FMT_STRING("{:08x}"), value); } if (n.exp_ > 0) - out = format_to(out, FMT_STRING("p{}"), - n.exp_ * detail::bigint::bigit_bits); + out = fmt::format_to(out, FMT_STRING("p{}"), + n.exp_ * detail::bigint::bigit_bits); return out; } }; @@ -1436,7 +1423,7 @@ FMT_FUNC void report_system_error(int error_code, report_error(format_system_error, error_code, message); } -FMT_FUNC std::string vformat(string_view fmt, format_args args) { +FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { // Don't optimize the "{}" case to keep the binary size small and because it // can be better optimized in fmt::format anyway. auto buffer = memory_buffer(); @@ -1445,33 +1432,43 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) { } namespace detail { -#ifndef _WIN32 -FMT_FUNC bool write_console(std::FILE*, string_view) { return false; } +#if !defined(_WIN32) || defined(FMT_WINDOWS_NO_WCHAR) +FMT_FUNC auto write_console(int, string_view) -> bool { return false; } +FMT_FUNC auto write_console(std::FILE*, string_view) -> bool { return false; } #else using dword = conditional_t; extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // void*, const void*, dword, dword*, void*); -FMT_FUNC bool write_console(std::FILE* f, string_view text) { - auto fd = _fileno(f); - if (!_isatty(fd)) return false; +FMT_FUNC bool write_console(int fd, string_view text) { auto u16 = utf8_to_utf16(text); - auto written = dword(); return WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), - static_cast(u16.size()), &written, nullptr); + static_cast(u16.size()), nullptr, nullptr) != 0; } +FMT_FUNC auto write_console(std::FILE* f, string_view text) -> bool { + return write_console(_fileno(f), text); +} +#endif + +#ifdef _WIN32 // Print assuming legacy (non-Unicode) encoding. FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args) { auto buffer = memory_buffer(); - detail::vformat_to(buffer, fmt, - basic_format_args>(args)); - fwrite_fully(buffer.data(), 1, buffer.size(), f); + detail::vformat_to(buffer, fmt, args); + fwrite_fully(buffer.data(), buffer.size(), f); } #endif FMT_FUNC void print(std::FILE* f, string_view text) { - if (!write_console(f, text)) fwrite_fully(text.data(), 1, text.size(), f); +#ifdef _WIN32 + int fd = _fileno(f); + if (_isatty(fd)) { + std::fflush(f); + if (write_console(fd, text)) return; + } +#endif + fwrite_fully(text.data(), text.size(), f); } } // namespace detail diff --git a/thirdparty/fmt/include/fmt/format.h b/thirdparty/fmt/include/fmt/format.h index ed8b29eba..7637c8a0d 100644 --- a/thirdparty/fmt/include/fmt/format.h +++ b/thirdparty/fmt/include/fmt/format.h @@ -43,14 +43,15 @@ #include // std::system_error #ifdef __cpp_lib_bit_cast -# include // std::bitcast +# include // std::bit_cast #endif #include "core.h" -#ifndef FMT_BEGIN_DETAIL_NAMESPACE -# define FMT_BEGIN_DETAIL_NAMESPACE namespace detail { -# define FMT_END_DETAIL_NAMESPACE } +#if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L +# define FMT_INLINE_VARIABLE inline +#else +# define FMT_INLINE_VARIABLE #endif #if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) @@ -78,16 +79,25 @@ # endif #endif -#if FMT_GCC_VERSION -# define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) -#else -# define FMT_GCC_VISIBILITY_HIDDEN +#ifndef FMT_NO_UNIQUE_ADDRESS +# if FMT_CPLUSPLUS >= 202002L +# if FMT_HAS_CPP_ATTRIBUTE(no_unique_address) +# define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] +// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485) +# elif (FMT_MSC_VERSION >= 1929) && !FMT_CLANG_VERSION +# define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +# endif +# endif +#endif +#ifndef FMT_NO_UNIQUE_ADDRESS +# define FMT_NO_UNIQUE_ADDRESS #endif -#ifdef __NVCC__ -# define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) +// Visibility when compiled as a shared library/object. +#if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_SO_VISIBILITY(value) FMT_VISIBILITY(value) #else -# define FMT_CUDA_VERSION 0 +# define FMT_SO_VISIBILITY(value) #endif #ifdef __has_builtin @@ -120,10 +130,8 @@ FMT_END_NAMESPACE # define FMT_THROW(x) throw x # endif # else -# define FMT_THROW(x) \ - do { \ - FMT_ASSERT(false, (x).what()); \ - } while (false) +# define FMT_THROW(x) \ + ::fmt::detail::assert_fail(__FILE__, __LINE__, (x).what()) # endif #endif @@ -145,7 +153,10 @@ FMT_END_NAMESPACE #ifndef FMT_USE_USER_DEFINED_LITERALS // EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs. -# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ +// +// GCC before 4.9 requires a space in `operator"" _a` which is invalid in later +// compiler versions. +# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 409 || \ FMT_MSC_VERSION >= 1900) && \ (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= /* UDL feature */ 480) # define FMT_USE_USER_DEFINED_LITERALS 1 @@ -266,19 +277,6 @@ FMT_END_NAMESPACE #endif FMT_BEGIN_NAMESPACE - -template struct disjunction : std::false_type {}; -template struct disjunction

: P {}; -template -struct disjunction - : conditional_t> {}; - -template struct conjunction : std::true_type {}; -template struct conjunction

: P {}; -template -struct conjunction - : conditional_t, P1> {}; - namespace detail { FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { @@ -300,37 +298,6 @@ template constexpr CharT string_literal::value[sizeof...(C)]; #endif -template class formatbuf : public Streambuf { - private: - using char_type = typename Streambuf::char_type; - using streamsize = decltype(std::declval().sputn(nullptr, 0)); - using int_type = typename Streambuf::int_type; - using traits_type = typename Streambuf::traits_type; - - buffer& buffer_; - - public: - explicit formatbuf(buffer& buf) : buffer_(buf) {} - - protected: - // The put area is always empty. This makes the implementation simpler and has - // the advantage that the streambuf and the buffer are always in sync and - // sputc never writes into uninitialized memory. A disadvantage is that each - // call to sputc always results in a (virtual) call to overflow. There is no - // disadvantage here for sputn since this always results in a call to xsputn. - - auto overflow(int_type ch) -> int_type override { - if (!traits_type::eq_int_type(ch, traits_type::eof())) - buffer_.push_back(static_cast(ch)); - return ch; - } - - auto xsputn(const char_type* s, streamsize count) -> streamsize override { - buffer_.append(s, s + count); - return count; - } -}; - // Implementation of std::bit_cast for pre-C++20. template FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { @@ -362,14 +329,12 @@ class uint128_fallback { private: uint64_t lo_, hi_; - friend uint128_fallback umul128(uint64_t x, uint64_t y) noexcept; - public: constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} - constexpr uint64_t high() const noexcept { return hi_; } - constexpr uint64_t low() const noexcept { return lo_; } + constexpr auto high() const noexcept -> uint64_t { return hi_; } + constexpr auto low() const noexcept -> uint64_t { return lo_; } template ::value)> constexpr explicit operator T() const { @@ -445,7 +410,7 @@ class uint128_fallback { hi_ &= n.hi_; } - FMT_CONSTEXPR20 uint128_fallback& operator+=(uint64_t n) noexcept { + FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& { if (is_constant_evaluated()) { lo_ += n; hi_ += (lo_ < n ? 1 : 0); @@ -536,6 +501,8 @@ FMT_INLINE void assume(bool condition) { (void)condition; #if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION __builtin_assume(condition); +#elif FMT_GCC_VERSION + if (!condition) __builtin_unreachable(); #endif } @@ -554,20 +521,6 @@ inline auto get_data(Container& c) -> typename Container::value_type* { return c.data(); } -#if defined(_SECURE_SCL) && _SECURE_SCL -// Make a checked iterator to avoid MSVC warnings. -template using checked_ptr = stdext::checked_array_iterator; -template -constexpr auto make_checked(T* p, size_t size) -> checked_ptr { - return {p, size}; -} -#else -template using checked_ptr = T*; -template constexpr auto make_checked(T* p, size_t) -> T* { - return p; -} -#endif - // Attempts to reserve space for n extra characters in the output range. // Returns a pointer to the reserved range or a reference to it. template ::value)> @@ -575,12 +528,12 @@ template ::value)> __attribute__((no_sanitize("undefined"))) #endif inline auto -reserve(std::back_insert_iterator it, size_t n) - -> checked_ptr { +reserve(std::back_insert_iterator it, size_t n) -> + typename Container::value_type* { Container& c = get_container(it); size_t size = c.size(); c.resize(size + n); - return make_checked(get_data(c) + size, n); + return get_data(c) + size; } template @@ -612,8 +565,8 @@ template auto to_pointer(buffer_appender it, size_t n) -> T* { } template ::value)> -inline auto base_iterator(std::back_insert_iterator& it, - checked_ptr) +inline auto base_iterator(std::back_insert_iterator it, + typename Container::value_type*) -> std::back_insert_iterator { return it; } @@ -747,7 +700,7 @@ inline auto compute_width(basic_string_view s) -> size_t { } // Computes approximate display width of a UTF-8 string. -FMT_CONSTEXPR inline size_t compute_width(string_view s) { +FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t { size_t num_code_points = 0; // It is not a lambda for compatibility with C++14. struct count_code_points { @@ -794,12 +747,17 @@ inline auto code_point_index(basic_string_view s, size_t n) -> size_t { // Calculates the index of the nth code point in a UTF-8 string. inline auto code_point_index(string_view s, size_t n) -> size_t { - const char* data = s.data(); - size_t num_code_points = 0; - for (size_t i = 0, size = s.size(); i != size; ++i) { - if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i; - } - return s.size(); + size_t result = s.size(); + const char* begin = s.begin(); + for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) { + if (n != 0) { + --n; + return true; + } + result = to_unsigned(sv.begin() - begin); + return false; + }); + return result; } inline auto code_point_index(basic_string_view s, size_t n) @@ -881,7 +839,7 @@ void buffer::append(const U* begin, const U* end) { try_reserve(size_ + count); auto free_cap = capacity_ - size_; if (free_cap < count) count = free_cap; - std::uninitialized_copy_n(begin, count, make_checked(ptr_ + size_, count)); + std::uninitialized_copy_n(begin, count, ptr_ + size_); size_ += count; begin += count; } @@ -909,7 +867,7 @@ enum { inline_buffer_size = 500 }; **Example**:: auto out = fmt::memory_buffer(); - format_to(std::back_inserter(out), "The answer is {}.", 42); + fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); This will append the following output to the ``out`` object: @@ -926,8 +884,8 @@ class basic_memory_buffer final : public detail::buffer { private: T store_[SIZE]; - // Don't inherit from Allocator avoid generating type_info for it. - Allocator alloc_; + // Don't inherit from Allocator to avoid generating type_info for it. + FMT_NO_UNIQUE_ADDRESS Allocator alloc_; // Deallocate memory allocated by the buffer. FMT_CONSTEXPR20 void deallocate() { @@ -948,9 +906,10 @@ class basic_memory_buffer final : public detail::buffer { T* old_data = this->data(); T* new_data = std::allocator_traits::allocate(alloc_, new_capacity); + // Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481). + detail::assume(this->size() <= new_capacity); // The following code doesn't throw, so the raw pointer above doesn't leak. - std::uninitialized_copy(old_data, old_data + this->size(), - detail::make_checked(new_data, new_capacity)); + std::uninitialized_copy_n(old_data, this->size(), new_data); this->set(new_data, new_capacity); // deallocate must not throw according to the standard, but even if it does, // the buffer already uses the new storage and will deallocate it in @@ -978,8 +937,7 @@ class basic_memory_buffer final : public detail::buffer { size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { this->set(store_, capacity); - detail::copy_str(other.store_, other.store_ + size, - detail::make_checked(store_, capacity)); + detail::copy_str(other.store_, other.store_ + size, store_); } else { this->set(data, capacity); // Set pointer to the inline array so that delete is not called @@ -1025,7 +983,6 @@ class basic_memory_buffer final : public detail::buffer { /** Increases the buffer capacity to *new_capacity*. */ void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } - // Directly append data into the buffer using detail::buffer::append; template void append(const ContiguousRange& range) { @@ -1041,9 +998,11 @@ struct is_contiguous> : std::true_type { FMT_END_EXPORT namespace detail { -FMT_API bool write_console(std::FILE* f, string_view text); +FMT_API auto write_console(int fd, string_view text) -> bool; +FMT_API auto write_console(std::FILE* f, string_view text) -> bool; FMT_API void print(std::FILE*, string_view); } // namespace detail + FMT_BEGIN_EXPORT // Suppress a misleading warning in older versions of clang. @@ -1052,7 +1011,7 @@ FMT_BEGIN_EXPORT #endif /** An error reported from a formatting function. */ -class FMT_API format_error : public std::runtime_error { +class FMT_SO_VISIBILITY("default") format_error : public std::runtime_error { public: using std::runtime_error::runtime_error; }; @@ -1128,7 +1087,7 @@ template class format_facet : public Locale::facet { } }; -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. @@ -1159,13 +1118,13 @@ using uint32_or_64_or_128_t = template using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; -#define FMT_POWERS_OF_10(factor) \ - factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ - (factor)*1000000, (factor)*10000000, (factor)*100000000, \ - (factor)*1000000000 +#define FMT_POWERS_OF_10(factor) \ + factor * 10, (factor) * 100, (factor) * 1000, (factor) * 10000, \ + (factor) * 100000, (factor) * 1000000, (factor) * 10000000, \ + (factor) * 100000000, (factor) * 1000000000 // Converts value in the range [0, 100) to a string. -constexpr const char* digits2(size_t value) { +constexpr auto digits2(size_t value) -> const char* { // GCC generates slightly better code when value is pointer-size. return &"0001020304050607080910111213141516171819" "2021222324252627282930313233343536373839" @@ -1175,7 +1134,7 @@ constexpr const char* digits2(size_t value) { } // Sign is a template parameter to workaround a bug in gcc 4.8. -template constexpr Char sign(Sign s) { +template constexpr auto sign(Sign s) -> Char { #if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 604 static_assert(std::is_same::value, ""); #endif @@ -1257,7 +1216,7 @@ FMT_CONSTEXPR auto count_digits(UInt n) -> int { FMT_INLINE auto do_count_digits(uint32_t n) -> int { // An optimization by Kendall Willets from https://bit.ly/3uOIQrB. // This increments the upper 32 bits (log10(T) - 1) when >= T is added. -# define FMT_INC(T) (((sizeof(# T) - 1ull) << 32) - T) +# define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) static constexpr uint64_t table[] = { FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 @@ -1393,14 +1352,14 @@ FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits, } template -inline auto format_uint(It out, UInt value, int num_digits, bool upper = false) - -> It { +FMT_CONSTEXPR inline auto format_uint(It out, UInt value, int num_digits, + bool upper = false) -> It { if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { format_uint(ptr, value, num_digits, upper); return out; } // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1). - char buffer[num_bits() / BASE_BITS + 1]; + char buffer[num_bits() / BASE_BITS + 1] = {}; format_uint(buffer, value, num_digits, upper); return detail::copy_str_noinline(buffer, buffer + num_digits, out); } @@ -1418,47 +1377,54 @@ class utf8_to_utf16 { auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; +enum class to_utf8_error_policy { abort, replace }; + // A converter from UTF-16/UTF-32 (host endian) to UTF-8. -template -class unicode_to_utf8 { +template class to_utf8 { private: Buffer buffer_; public: - unicode_to_utf8() {} - explicit unicode_to_utf8(basic_string_view s) { + to_utf8() {} + explicit to_utf8(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) { static_assert(sizeof(WChar) == 2 || sizeof(WChar) == 4, "Expect utf16 or utf32"); - - if (!convert(s)) + if (!convert(s, policy)) FMT_THROW(std::runtime_error(sizeof(WChar) == 2 ? "invalid utf16" : "invalid utf32")); } operator string_view() const { return string_view(&buffer_[0], size()); } - size_t size() const { return buffer_.size() - 1; } - const char* c_str() const { return &buffer_[0]; } - std::string str() const { return std::string(&buffer_[0], size()); } + auto size() const -> size_t { return buffer_.size() - 1; } + auto c_str() const -> const char* { return &buffer_[0]; } + auto str() const -> std::string { return std::string(&buffer_[0], size()); } // Performs conversion returning a bool instead of throwing exception on // conversion error. This method may still throw in case of memory allocation // error. - bool convert(basic_string_view s) { - if (!convert(buffer_, s)) return false; + auto convert(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { + if (!convert(buffer_, s, policy)) return false; buffer_.push_back(0); return true; } - static bool convert(Buffer& buf, basic_string_view s) { + static auto convert(Buffer& buf, basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { for (auto p = s.begin(); p != s.end(); ++p) { uint32_t c = static_cast(*p); if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) { - // surrogate pair + // Handle a surrogate pair. ++p; if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { - return false; + if (policy == to_utf8_error_policy::abort) return false; + buf.append(string_view("\xEF\xBF\xBD")); + --p; + } else { + c = (c << 10) + static_cast(*p) - 0x35fdc00; } - c = (c << 10) + static_cast(*p) - 0x35fdc00; - } - if (c < 0x80) { + } else if (c < 0x80) { buf.push_back(static_cast(c)); } else if (c < 0x800) { buf.push_back(static_cast(0xc0 | (c >> 6))); @@ -1481,14 +1447,14 @@ class unicode_to_utf8 { }; // Computes 128-bit result of multiplication of two 64-bit unsigned integers. -inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { +inline auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback { #if FMT_USE_INT128 auto p = static_cast(x) * static_cast(y); return {static_cast(p >> 64), static_cast(p)}; #elif defined(_MSC_VER) && defined(_M_X64) - auto result = uint128_fallback(); - result.lo_ = _umul128(x, y, &result.hi_); - return result; + auto hi = uint64_t(); + auto lo = _umul128(x, y, &hi); + return {hi, lo}; #else const uint64_t mask = static_cast(max_value()); @@ -1512,19 +1478,19 @@ inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { namespace dragonbox { // Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from // https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. -inline int floor_log10_pow2(int e) noexcept { +inline auto floor_log10_pow2(int e) noexcept -> int { FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); return (e * 315653) >> 20; } -inline int floor_log2_pow10(int e) noexcept { +inline auto floor_log2_pow10(int e) noexcept -> int { FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); return (e * 1741647) >> 19; } // Computes upper 64 bits of multiplication of two 64-bit unsigned integers. -inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept { +inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t { #if FMT_USE_INT128 auto p = static_cast(x) * static_cast(y); return static_cast(p >> 64); @@ -1537,14 +1503,14 @@ inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept { // Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. -inline uint128_fallback umul192_upper128(uint64_t x, - uint128_fallback y) noexcept { +inline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { uint128_fallback r = umul128(x, y.high()); r += umul128_upper64(x, y.low()); return r; } -FMT_API uint128_fallback get_cached_power(int k) noexcept; +FMT_API auto get_cached_power(int k) noexcept -> uint128_fallback; // Type-specific information that Dragonbox uses. template struct float_info; @@ -1598,14 +1564,14 @@ template FMT_API auto to_decimal(T x) noexcept -> decimal_fp; } // namespace dragonbox // Returns true iff Float has the implicit bit which is not stored. -template constexpr bool has_implicit_bit() { +template constexpr auto has_implicit_bit() -> bool { // An 80-bit FP number has a 64-bit significand an no implicit bit. return std::numeric_limits::digits != 64; } // Returns the number of significand bits stored in Float. The implicit bit is // not counted since it is not stored. -template constexpr int num_significand_bits() { +template constexpr auto num_significand_bits() -> int { // std::numeric_limits may not support __float128. return is_float128() ? 112 : (std::numeric_limits::digits - @@ -1698,7 +1664,7 @@ using fp = basic_fp; // Normalizes the value converted from double and multiplied by (1 << SHIFT). template -FMT_CONSTEXPR basic_fp normalize(basic_fp value) { +FMT_CONSTEXPR auto normalize(basic_fp value) -> basic_fp { // Handle subnormals. const auto implicit_bit = F(1) << num_significand_bits(); const auto shifted_implicit_bit = implicit_bit << SHIFT; @@ -1715,7 +1681,7 @@ FMT_CONSTEXPR basic_fp normalize(basic_fp value) { } // Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. -FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { +FMT_CONSTEXPR inline auto multiply(uint64_t lhs, uint64_t rhs) -> uint64_t { #if FMT_USE_INT128 auto product = static_cast<__uint128_t>(lhs) * rhs; auto f = static_cast(product >> 64); @@ -1732,124 +1698,13 @@ FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { #endif } -FMT_CONSTEXPR inline fp operator*(fp x, fp y) { +FMT_CONSTEXPR inline auto operator*(fp x, fp y) -> fp { return {multiply(x.f, y.f), x.e + y.e + 64}; } -template struct basic_data { - // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. - // These are generated by support/compute-powers.py. - static constexpr uint64_t pow10_significands[87] = { - 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, - 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, - 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, - 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, - 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, - 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, - 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, - 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, - 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, - 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, - 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, - 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, - 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, - 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, - 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, - 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, - 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, - 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, - 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, - 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, - 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, - 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, - 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, - 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, - 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, - 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, - 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, - 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, - 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, - }; - -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wnarrowing" -#endif - // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding - // to significands above. - static constexpr int16_t pow10_exponents[87] = { - -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, - -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, - -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, - -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, - -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, - 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, - 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, - 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066}; -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 -# pragma GCC diagnostic pop -#endif - - static constexpr uint64_t power_of_10_64[20] = { - 1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL), - 10000000000000000000ULL}; - - // For checking rounding thresholds. - // The kth entry is chosen to be the smallest integer such that the - // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. - static constexpr uint32_t fractional_part_rounding_thresholds[8] = { - 2576980378, // ceil(2^31 + 2^32/10^1) - 2190433321, // ceil(2^31 + 2^32/10^2) - 2151778616, // ceil(2^31 + 2^32/10^3) - 2147913145, // ceil(2^31 + 2^32/10^4) - 2147526598, // ceil(2^31 + 2^32/10^5) - 2147487943, // ceil(2^31 + 2^32/10^6) - 2147484078, // ceil(2^31 + 2^32/10^7) - 2147483691 // ceil(2^31 + 2^32/10^8) - }; -}; - -#if FMT_CPLUSPLUS < 201703L -template constexpr uint64_t basic_data::pow10_significands[]; -template constexpr int16_t basic_data::pow10_exponents[]; -template constexpr uint64_t basic_data::power_of_10_64[]; -template -constexpr uint32_t basic_data::fractional_part_rounding_thresholds[]; -#endif - -// This is a struct rather than an alias to avoid shadowing warnings in gcc. -struct data : basic_data<> {}; - -// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its -// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`. -FMT_CONSTEXPR inline fp get_cached_power(int min_exponent, - int& pow10_exponent) { - const int shift = 32; - // log10(2) = 0x0.4d104d427de7fbcc... - const int64_t significand = 0x4d104d427de7fbcc; - int index = static_cast( - ((min_exponent + fp::num_significand_bits - 1) * (significand >> shift) + - ((int64_t(1) << shift) - 1)) // ceil - >> 32 // arithmetic shift - ); - // Decimal exponent of the first (smallest) cached power of 10. - const int first_dec_exp = -348; - // Difference between 2 consecutive decimal exponents in cached powers of 10. - const int dec_exp_step = 8; - index = (index - first_dec_exp - 1) / dec_exp_step + 1; - pow10_exponent = first_dec_exp + index * dec_exp_step; - // Using *(x + index) instead of x[index] avoids an issue with some compilers - // using the EDG frontend (e.g. nvhpc/22.3 in C++17 mode). - return {*(data::pow10_significands + index), - *(data::pow10_exponents + index)}; -} - -template +template () == num_bits()> using convert_float_result = - conditional_t::value || - std::numeric_limits::digits == - std::numeric_limits::digits, - double, T>; + conditional_t::value || doublish, double, T>; template constexpr auto convert_float(T value) -> convert_float_result { @@ -1970,7 +1825,7 @@ inline auto find_escape(const char* begin, const char* end) [] { \ /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ /* Use a macro-like name to avoid shadowing warnings. */ \ - struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \ + struct FMT_VISIBILITY("hidden") FMT_COMPILE_STRING : base { \ using char_type FMT_MAYBE_UNUSED = fmt::remove_cvref_t; \ FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \ operator fmt::basic_string_view() const { \ @@ -2065,11 +1920,13 @@ auto write_escaped_string(OutputIt out, basic_string_view str) template auto write_escaped_char(OutputIt out, Char v) -> OutputIt { + Char v_array[1] = {v}; *out++ = static_cast('\''); if ((needs_escape(static_cast(v)) && v != static_cast('"')) || v == static_cast('\'')) { - out = write_escaped_cp( - out, find_escape_result{&v, &v + 1, static_cast(v)}); + out = write_escaped_cp(out, + find_escape_result{v_array, v_array + 1, + static_cast(v)}); } else { *out++ = v; } @@ -2158,10 +2015,10 @@ template class digit_grouping { std::string::const_iterator group; int pos; }; - next_state initial_state() const { return {grouping_.begin(), 0}; } + auto initial_state() const -> next_state { return {grouping_.begin(), 0}; } // Returns the next digit group separator position. - int next(next_state& state) const { + auto next(next_state& state) const -> int { if (thousands_sep_.empty()) return max_value(); if (state.group == grouping_.end()) return state.pos += grouping_.back(); if (*state.group <= 0 || *state.group == max_value()) @@ -2180,9 +2037,9 @@ template class digit_grouping { digit_grouping(std::string grouping, std::basic_string sep) : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} - bool has_separator() const { return !thousands_sep_.empty(); } + auto has_separator() const -> bool { return !thousands_sep_.empty(); } - int count_separators(int num_digits) const { + auto count_separators(int num_digits) const -> int { int count = 0; auto state = initial_state(); while (num_digits > next(state)) ++count; @@ -2191,7 +2048,7 @@ template class digit_grouping { // Applies grouping to digits and write the output to out. template - Out apply(Out out, basic_string_view digits) const { + auto apply(Out out, basic_string_view digits) const -> Out { auto num_digits = static_cast(digits.size()); auto separators = basic_memory_buffer(); separators.push_back(0); @@ -2214,24 +2071,66 @@ template class digit_grouping { } }; +FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { + prefix |= prefix != 0 ? value << 8 : value; + prefix += (1u + (value > 0xff ? 1 : 0)) << 24; +} + // Writes a decimal integer with digit grouping. template auto write_int(OutputIt out, UInt value, unsigned prefix, const format_specs& specs, const digit_grouping& grouping) -> OutputIt { static_assert(std::is_same, UInt>::value, ""); - int num_digits = count_digits(value); - char digits[40]; - format_decimal(digits, value, num_digits); - unsigned size = to_unsigned((prefix != 0 ? 1 : 0) + num_digits + - grouping.count_separators(num_digits)); + int num_digits = 0; + auto buffer = memory_buffer(); + switch (specs.type) { + case presentation_type::none: + case presentation_type::dec: { + num_digits = count_digits(value); + format_decimal(appender(buffer), value, num_digits); + break; + } + case presentation_type::hex_lower: + case presentation_type::hex_upper: { + bool upper = specs.type == presentation_type::hex_upper; + if (specs.alt) + prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0'); + num_digits = count_digits<4>(value); + format_uint<4, char>(appender(buffer), value, num_digits, upper); + break; + } + case presentation_type::bin_lower: + case presentation_type::bin_upper: { + bool upper = specs.type == presentation_type::bin_upper; + if (specs.alt) + prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0'); + num_digits = count_digits<1>(value); + format_uint<1, char>(appender(buffer), value, num_digits); + break; + } + case presentation_type::oct: { + num_digits = count_digits<3>(value); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + if (specs.alt && specs.precision <= num_digits && value != 0) + prefix_append(prefix, '0'); + format_uint<3, char>(appender(buffer), value, num_digits); + break; + } + case presentation_type::chr: + return write_char(out, static_cast(value), specs); + default: + throw_format_error("invalid format specifier"); + } + + unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) + + to_unsigned(grouping.count_separators(num_digits)); return write_padded( out, specs, size, size, [&](reserve_iterator it) { - if (prefix != 0) { - char sign = static_cast(prefix); - *it++ = static_cast(sign); - } - return grouping.apply(it, string_view(digits, to_unsigned(num_digits))); + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + return grouping.apply(it, string_view(buffer.data(), buffer.size())); }); } @@ -2244,11 +2143,6 @@ inline auto write_loc(OutputIt, loc_value, const format_specs&, return false; } -FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { - prefix |= prefix != 0 ? value << 8 : value; - prefix += (1u + (value > 0xff ? 1 : 0)) << 24; -} - template struct write_int_arg { UInt abs_value; unsigned prefix; @@ -2395,25 +2289,25 @@ class counting_iterator { FMT_CONSTEXPR counting_iterator() : count_(0) {} - FMT_CONSTEXPR size_t count() const { return count_; } + FMT_CONSTEXPR auto count() const -> size_t { return count_; } - FMT_CONSTEXPR counting_iterator& operator++() { + FMT_CONSTEXPR auto operator++() -> counting_iterator& { ++count_; return *this; } - FMT_CONSTEXPR counting_iterator operator++(int) { + FMT_CONSTEXPR auto operator++(int) -> counting_iterator { auto it = *this; ++*this; return it; } - FMT_CONSTEXPR friend counting_iterator operator+(counting_iterator it, - difference_type n) { + FMT_CONSTEXPR friend auto operator+(counting_iterator it, difference_type n) + -> counting_iterator { it.count_ += static_cast(n); return it; } - FMT_CONSTEXPR value_type operator*() const { return {}; } + FMT_CONSTEXPR auto operator*() const -> value_type { return {}; } }; template @@ -2448,9 +2342,10 @@ template FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const format_specs& specs, locale_ref) -> OutputIt { - return specs.type != presentation_type::pointer - ? write(out, basic_string_view(s), specs, {}) - : write_ptr(out, bit_cast(s), &specs); + if (specs.type == presentation_type::pointer) + return write_ptr(out, bit_cast(s), &specs); + if (!s) throw_format_error("string pointer is null"); + return write(out, basic_string_view(s), specs, {}); } template OutputIt { return base_iterator(out, it); } +// DEPRECATED! +template +FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, + format_specs& specs) -> const Char* { + FMT_ASSERT(begin != end, ""); + auto align = align::none; + auto p = begin + code_point_length(begin); + if (end - p <= 0) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': + align = align::left; + break; + case '>': + align = align::right; + break; + case '^': + align = align::center; + break; + } + if (align != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '}') return begin; + if (c == '{') { + throw_format_error("invalid fill character '{'"); + return begin; + } + specs.fill = {begin, to_unsigned(p - begin)}; + begin = p + 1; + } else { + ++begin; + } + break; + } else if (p == begin) { + break; + } + p = begin; + } + specs.align = align; + return begin; +} + // A floating-point presentation format. enum class float_format : unsigned char { general, // General: exponent notation or fixed point based on magnitude. @@ -2493,9 +2431,8 @@ struct float_specs { bool showpoint : 1; }; -template -FMT_CONSTEXPR auto parse_float_type_spec(const format_specs& specs, - ErrorHandler&& eh = {}) +template +FMT_CONSTEXPR auto parse_float_type_spec(const format_specs& specs) -> float_specs { auto result = float_specs(); result.showpoint = specs.alt; @@ -2531,7 +2468,7 @@ FMT_CONSTEXPR auto parse_float_type_spec(const format_specs& specs, result.format = float_format::hex; break; default: - eh.on_error("invalid format specifier"); + throw_format_error("invalid format specifier"); break; } return result; @@ -2770,12 +2707,12 @@ template class fallback_digit_grouping { public: constexpr fallback_digit_grouping(locale_ref, bool) {} - constexpr bool has_separator() const { return false; } + constexpr auto has_separator() const -> bool { return false; } - constexpr int count_separators(int) const { return 0; } + constexpr auto count_separators(int) const -> int { return 0; } template - constexpr Out apply(Out out, basic_string_view) const { + constexpr auto apply(Out out, basic_string_view) const -> Out { return out; } }; @@ -2794,7 +2731,7 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, } } -template constexpr bool isnan(T value) { +template constexpr auto isnan(T value) -> bool { return !(value >= value); // std::isnan doesn't support __float128. } @@ -2807,14 +2744,14 @@ struct has_isfinite> template ::value&& has_isfinite::value)> -FMT_CONSTEXPR20 bool isfinite(T value) { +FMT_CONSTEXPR20 auto isfinite(T value) -> bool { constexpr T inf = T(std::numeric_limits::infinity()); if (is_constant_evaluated()) return !detail::isnan(value) && value < inf && value > -inf; return std::isfinite(value); } template ::value)> -FMT_CONSTEXPR bool isfinite(T value) { +FMT_CONSTEXPR auto isfinite(T value) -> bool { T inf = T(std::numeric_limits::infinity()); // std::isfinite doesn't support __float128. return !detail::isnan(value) && value < inf && value > -inf; @@ -2833,78 +2770,6 @@ FMT_INLINE FMT_CONSTEXPR bool signbit(T value) { return std::signbit(static_cast(value)); } -enum class round_direction { unknown, up, down }; - -// Given the divisor (normally a power of 10), the remainder = v % divisor for -// some number v and the error, returns whether v should be rounded up, down, or -// whether the rounding direction can't be determined due to error. -// error should be less than divisor / 2. -FMT_CONSTEXPR inline round_direction get_round_direction(uint64_t divisor, - uint64_t remainder, - uint64_t error) { - FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow. - FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow. - FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow. - // Round down if (remainder + error) * 2 <= divisor. - if (remainder <= divisor - remainder && error * 2 <= divisor - remainder * 2) - return round_direction::down; - // Round up if (remainder - error) * 2 >= divisor. - if (remainder >= error && - remainder - error >= divisor - (remainder - error)) { - return round_direction::up; - } - return round_direction::unknown; -} - -namespace digits { -enum result { - more, // Generate more digits. - done, // Done generating digits. - error // Digit generation cancelled due to an error. -}; -} - -struct gen_digits_handler { - char* buf; - int size; - int precision; - int exp10; - bool fixed; - - FMT_CONSTEXPR digits::result on_digit(char digit, uint64_t divisor, - uint64_t remainder, uint64_t error, - bool integral) { - FMT_ASSERT(remainder < divisor, ""); - buf[size++] = digit; - if (!integral && error >= remainder) return digits::error; - if (size < precision) return digits::more; - if (!integral) { - // Check if error * 2 < divisor with overflow prevention. - // The check is not needed for the integral part because error = 1 - // and divisor > (1 << 32) there. - if (error >= divisor || error >= divisor - error) return digits::error; - } else { - FMT_ASSERT(error == 1 && divisor > 2, ""); - } - auto dir = get_round_direction(divisor, remainder, error); - if (dir != round_direction::up) - return dir == round_direction::down ? digits::done : digits::error; - ++buf[size - 1]; - for (int i = size - 1; i > 0 && buf[i] > '9'; --i) { - buf[i] = '0'; - ++buf[i - 1]; - } - if (buf[0] > '9') { - buf[0] = '1'; - if (fixed) - buf[size++] = '0'; - else - ++exp10; - } - return digits::done; - } -}; - inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { // Adjust fixed precision by exponent because it is relative to decimal // point. @@ -2913,101 +2778,6 @@ inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { precision += exp10; } -// Generates output using the Grisu digit-gen algorithm. -// error: the size of the region (lower, upper) outside of which numbers -// definitely do not round to value (Delta in Grisu3). -FMT_INLINE FMT_CONSTEXPR20 auto grisu_gen_digits(fp value, uint64_t error, - int& exp, - gen_digits_handler& handler) - -> digits::result { - const fp one(1ULL << -value.e, value.e); - // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be - // zero because it contains a product of two 64-bit numbers with MSB set (due - // to normalization) - 1, shifted right by at most 60 bits. - auto integral = static_cast(value.f >> -one.e); - FMT_ASSERT(integral != 0, ""); - FMT_ASSERT(integral == value.f >> -one.e, ""); - // The fractional part of scaled value (p2 in Grisu) c = value % one. - uint64_t fractional = value.f & (one.f - 1); - exp = count_digits(integral); // kappa in Grisu. - // Non-fixed formats require at least one digit and no precision adjustment. - if (handler.fixed) { - adjust_precision(handler.precision, exp + handler.exp10); - // Check if precision is satisfied just by leading zeros, e.g. - // format("{:.2f}", 0.001) gives "0.00" without generating any digits. - if (handler.precision <= 0) { - if (handler.precision < 0) return digits::done; - // Divide by 10 to prevent overflow. - uint64_t divisor = data::power_of_10_64[exp - 1] << -one.e; - auto dir = get_round_direction(divisor, value.f / 10, error * 10); - if (dir == round_direction::unknown) return digits::error; - handler.buf[handler.size++] = dir == round_direction::up ? '1' : '0'; - return digits::done; - } - } - // Generate digits for the integral part. This can produce up to 10 digits. - do { - uint32_t digit = 0; - auto divmod_integral = [&](uint32_t divisor) { - digit = integral / divisor; - integral %= divisor; - }; - // This optimization by Milo Yip reduces the number of integer divisions by - // one per iteration. - switch (exp) { - case 10: - divmod_integral(1000000000); - break; - case 9: - divmod_integral(100000000); - break; - case 8: - divmod_integral(10000000); - break; - case 7: - divmod_integral(1000000); - break; - case 6: - divmod_integral(100000); - break; - case 5: - divmod_integral(10000); - break; - case 4: - divmod_integral(1000); - break; - case 3: - divmod_integral(100); - break; - case 2: - divmod_integral(10); - break; - case 1: - digit = integral; - integral = 0; - break; - default: - FMT_ASSERT(false, "invalid number of digits"); - } - --exp; - auto remainder = (static_cast(integral) << -one.e) + fractional; - auto result = handler.on_digit(static_cast('0' + digit), - data::power_of_10_64[exp] << -one.e, - remainder, error, true); - if (result != digits::more) return result; - } while (exp > 0); - // Generate digits for the fractional part. - for (;;) { - fractional *= 10; - error *= 10; - char digit = static_cast('0' + (fractional >> -one.e)); - fractional &= one.f - 1; - --exp; - auto result = handler.on_digit(digit, one.f, fractional, error, false); - if (result != digits::more) return result; - } -} - class bigint { private: // A bigint is stored as an array of bigits (big digits), with bigit at index @@ -3018,10 +2788,10 @@ class bigint { basic_memory_buffer bigits_; int exp_; - FMT_CONSTEXPR20 bigit operator[](int index) const { + FMT_CONSTEXPR20 auto operator[](int index) const -> bigit { return bigits_[to_unsigned(index)]; } - FMT_CONSTEXPR20 bigit& operator[](int index) { + FMT_CONSTEXPR20 auto operator[](int index) -> bigit& { return bigits_[to_unsigned(index)]; } @@ -3108,7 +2878,7 @@ class bigint { auto size = other.bigits_.size(); bigits_.resize(size); auto data = other.bigits_.data(); - std::copy(data, data + size, make_checked(bigits_.data(), size)); + copy_str(data, data + size, bigits_.data()); exp_ = other.exp_; } @@ -3117,11 +2887,11 @@ class bigint { assign(uint64_or_128_t(n)); } - FMT_CONSTEXPR20 int num_bigits() const { + FMT_CONSTEXPR20 auto num_bigits() const -> int { return static_cast(bigits_.size()) + exp_; } - FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) { + FMT_NOINLINE FMT_CONSTEXPR20 auto operator<<=(int shift) -> bigint& { FMT_ASSERT(shift >= 0, ""); exp_ += shift / bigit_bits; shift %= bigit_bits; @@ -3136,13 +2906,15 @@ class bigint { return *this; } - template FMT_CONSTEXPR20 bigint& operator*=(Int value) { + template + FMT_CONSTEXPR20 auto operator*=(Int value) -> bigint& { FMT_ASSERT(value > 0, ""); multiply(uint32_or_64_or_128_t(value)); return *this; } - friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) { + friend FMT_CONSTEXPR20 auto compare(const bigint& lhs, const bigint& rhs) + -> int { int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); if (num_lhs_bigits != num_rhs_bigits) return num_lhs_bigits > num_rhs_bigits ? 1 : -1; @@ -3159,8 +2931,9 @@ class bigint { } // Returns compare(lhs1 + lhs2, rhs). - friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2, - const bigint& rhs) { + friend FMT_CONSTEXPR20 auto add_compare(const bigint& lhs1, + const bigint& lhs2, const bigint& rhs) + -> int { auto minimum = [](int a, int b) { return a < b ? a : b; }; auto maximum = [](int a, int b) { return a > b ? a : b; }; int max_lhs_bigits = maximum(lhs1.num_bigits(), lhs2.num_bigits()); @@ -3241,13 +3014,13 @@ class bigint { bigits_.resize(to_unsigned(num_bigits + exp_difference)); for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) bigits_[j] = bigits_[i]; - std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); + std::uninitialized_fill_n(bigits_.data(), exp_difference, 0u); exp_ -= exp_difference; } // Divides this bignum by divisor, assigning the remainder to this and // returning the quotient. - FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) { + FMT_CONSTEXPR20 auto divmod_assign(const bigint& divisor) -> int { FMT_ASSERT(this != &divisor, ""); if (compare(*this, divisor) < 0) return 0; FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); @@ -3322,6 +3095,7 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, } int even = static_cast((value.f & 1) == 0); if (!upper) upper = &lower; + bool shortest = num_digits < 0; if ((flags & dragon::fixup) != 0) { if (add_compare(numerator, *upper, denominator) + even <= 0) { --exp10; @@ -3334,7 +3108,7 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, if ((flags & dragon::fixed) != 0) adjust_precision(num_digits, exp10 + 1); } // Invariant: value == (numerator / denominator) * pow(10, exp10). - if (num_digits < 0) { + if (shortest) { // Generate the shortest representation. num_digits = 0; char* data = buf.data(); @@ -3364,7 +3138,7 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, } // Generate the given number of digits. exp10 -= num_digits - 1; - if (num_digits == 0) { + if (num_digits <= 0) { denominator *= 10; auto digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; buf.push_back(digit); @@ -3389,7 +3163,10 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, } if (buf[0] == overflow) { buf[0] = '1'; - ++exp10; + if ((flags & dragon::fixed) != 0) + buf.push_back('0'); + else + ++exp10; } return; } @@ -3486,6 +3263,17 @@ FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, format_hexfloat(static_cast(value), precision, specs, buf); } +constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t { + // For checking rounding thresholds. + // The kth entry is chosen to be the smallest integer such that the + // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. + // It is equal to ceil(2^31 + 2^32/10^(k + 1)). + // These are stored in a string literal because we cannot have static arrays + // in constexpr functions and non-static ones are poorly optimized. + return U"\x9999999a\x828f5c29\x80418938\x80068db9\x8000a7c6\x800010c7" + U"\x800001ae\x8000002b"[index]; +} + template FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, buffer& buf) -> int { @@ -3508,7 +3296,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, int exp = 0; bool use_dragon = true; unsigned dragon_flags = 0; - if (!is_fast_float()) { + if (!is_fast_float() || is_constant_evaluated()) { const auto inv_log2_10 = 0.3010299956639812; // 1 / log2(10) using info = dragonbox::float_info; const auto f = basic_fp(converted_value); @@ -3516,10 +3304,11 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, // 10^(exp - 1) <= value < 10^exp or 10^exp <= value < 10^(exp + 1). // This is based on log10(value) == log2(value) / log2(10) and approximation // of log2(value) by e + num_fraction_bits idea from double-conversion. - exp = static_cast( - std::ceil((f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10)); + auto e = (f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10; + exp = static_cast(e); + if (e > exp) ++exp; // Compute ceil. dragon_flags = dragon::fixup; - } else if (!is_constant_evaluated() && precision < 0) { + } else if (precision < 0) { // Use Dragonbox for the shortest format. if (specs.binary32) { auto dec = dragonbox::to_decimal(static_cast(value)); @@ -3529,25 +3318,6 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, auto dec = dragonbox::to_decimal(static_cast(value)); write(buffer_appender(buf), dec.significand); return dec.exponent; - } else if (is_constant_evaluated()) { - // Use Grisu + Dragon4 for the given precision: - // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. - const int min_exp = -60; // alpha in Grisu. - int cached_exp10 = 0; // K in Grisu. - fp normalized = normalize(fp(converted_value)); - const auto cached_pow = get_cached_power( - min_exp - (normalized.e + fp::num_significand_bits), cached_exp10); - normalized = normalized * cached_pow; - gen_digits_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; - if (grisu_gen_digits(normalized, 1, exp, handler) != digits::error && - !is_constant_evaluated()) { - exp += handler.exp10; - buf.try_resize(to_unsigned(handler.size)); - use_dragon = false; - } else { - exp += handler.size - cached_exp10 - 1; - precision = handler.precision; - } } else { // Extract significand bits and exponent bits. using info = dragonbox::float_info; @@ -3566,7 +3336,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, significand <<= 1; } else { // Normalize subnormal inputs. - FMT_ASSERT(significand != 0, "zeros should not appear hear"); + FMT_ASSERT(significand != 0, "zeros should not appear here"); int shift = countl_zero(significand); FMT_ASSERT(shift >= num_bits() - num_significand_bits(), ""); @@ -3603,9 +3373,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, } // Compute the actual number of decimal digits to print. - if (fixed) { - adjust_precision(precision, exp + digits_in_the_first_segment); - } + if (fixed) adjust_precision(precision, exp + digits_in_the_first_segment); // Use Dragon4 only when there might be not enough digits in the first // segment. @@ -3710,12 +3478,12 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, // fractional part is strictly larger than 1/2. if (precision < 9) { uint32_t fractional_part = static_cast(prod); - should_round_up = fractional_part >= - data::fractional_part_rounding_thresholds - [8 - number_of_digits_to_print] || - ((fractional_part >> 31) & - ((digits & 1) | (second_third_subsegments != 0) | - has_more_segments)) != 0; + should_round_up = + fractional_part >= fractional_part_rounding_thresholds( + 8 - number_of_digits_to_print) || + ((fractional_part >> 31) & + ((digits & 1) | (second_third_subsegments != 0) | + has_more_segments)) != 0; } // Rounding at the subsegment boundary. // In this case, the fractional part is at least 1/2 if and only if @@ -3750,12 +3518,12 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, // of 19 digits, so in this case the third segment should be // consisting of a genuine digit from the input. uint32_t fractional_part = static_cast(prod); - should_round_up = fractional_part >= - data::fractional_part_rounding_thresholds - [8 - number_of_digits_to_print] || - ((fractional_part >> 31) & - ((digits & 1) | (third_subsegment != 0) | - has_more_segments)) != 0; + should_round_up = + fractional_part >= fractional_part_rounding_thresholds( + 8 - number_of_digits_to_print) || + ((fractional_part >> 31) & + ((digits & 1) | (third_subsegment != 0) | + has_more_segments)) != 0; } // Rounding at the subsegment boundary. else { @@ -3987,8 +3755,11 @@ template enable_if_t::value == type::custom_type, OutputIt> { + auto formatter = typename Context::template formatter_type(); + auto parse_ctx = typename Context::parse_context_type({}); + formatter.parse(parse_ctx); auto ctx = Context(out, {}, {}); - return typename Context::template formatter_type().format(value, ctx); + return formatter.format(value, ctx); } // An argument visitor that formats the argument and writes it via the output @@ -4031,74 +3802,50 @@ template struct arg_formatter { } }; -template struct custom_formatter { - basic_format_parse_context& parse_ctx; - buffer_context& ctx; - - void operator()( - typename basic_format_arg>::handle h) const { - h.format(parse_ctx, ctx); - } - template void operator()(T) const {} -}; - -template class width_checker { - public: - explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} - +struct width_checker { template ::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { - if (is_negative(value)) handler_.on_error("negative width"); + if (is_negative(value)) throw_format_error("negative width"); return static_cast(value); } template ::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { - handler_.on_error("width is not integer"); + throw_format_error("width is not integer"); return 0; } - - private: - ErrorHandler& handler_; }; -template class precision_checker { - public: - explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {} - +struct precision_checker { template ::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { - if (is_negative(value)) handler_.on_error("negative precision"); + if (is_negative(value)) throw_format_error("negative precision"); return static_cast(value); } template ::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { - handler_.on_error("precision is not integer"); + throw_format_error("precision is not integer"); return 0; } - - private: - ErrorHandler& handler_; }; -template